Joystick refactor pt. Deux: Support Joysticks.

Add support for non-GameController SDL Joysticks.

Add proxy class wxSDLJoyDev to support using either SDL_GameController*
or SDL_Joystick* values in joystate.dev.

Add pretty much identical SDL code to support SDL_Joystick* when the
device cannot be opened as an SDL_GameController*, without changing the
API. SDL_Joystick* devices operate almost identically to SDL_Controller*
devices with their own default mappings, for both events and polling.

Filter axis motion events in the bindings editor widget for subsequent
events within 300ms. This gets rid of the double binding for +1/-1 when
the stick is moved to a direction.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
This commit is contained in:
Rafael Kitover 2020-05-04 22:12:01 +00:00
parent ad3327d437
commit 09e8da43db
4 changed files with 313 additions and 63 deletions

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-27 20:23+0000\n"
"POT-Creation-Date: 2020-05-04 01:24+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -831,11 +831,11 @@ msgid ""
"along with this program. If not, see http://www.gnu.org/licenses ."
msgstr ""
#: cmdevents.cpp:3069
#: cmdevents.cpp:3076
msgid "LAN link is already active. Disable link mode to disconnect."
msgstr ""
#: cmdevents.cpp:3075
#: cmdevents.cpp:3082
msgid "Network is not supported in local mode."
msgstr ""
@ -1016,78 +1016,78 @@ msgstr ""
msgid "Not a valid GBA cartridge"
msgstr ""
#: panel.cpp:1146
#: panel.cpp:1150
msgid "No memory for rewinding"
msgstr ""
#: panel.cpp:1156
#: panel.cpp:1160
msgid "Error writing rewind state"
msgstr ""
#: panel.cpp:2253
#: panel.cpp:2257
msgid "Failed to set glXSwapIntervalEXT"
msgstr ""
#: panel.cpp:2262
#: panel.cpp:2266
msgid "Failed to set glXSwapIntervalSGI"
msgstr ""
#: panel.cpp:2271
#: panel.cpp:2275
msgid "Failed to set glXSwapIntervalMESA"
msgstr ""
#: panel.cpp:2278
#: panel.cpp:2282
msgid "No support for wglGetExtensionsString"
msgstr ""
#: panel.cpp:2280
#: panel.cpp:2284
msgid "No support for WGL_EXT_swap_control"
msgstr ""
#: panel.cpp:2289
#: panel.cpp:2293
msgid "Failed to set wglSwapIntervalEXT"
msgstr ""
#: panel.cpp:2295
#: panel.cpp:2299
msgid "No VSYNC available on this platform"
msgstr ""
#: panel.cpp:2391
#: panel.cpp:2395
msgid "memory allocation error"
msgstr ""
#: panel.cpp:2394
#: panel.cpp:2398
msgid "error initializing codec"
msgstr ""
#: panel.cpp:2397
#: panel.cpp:2401
msgid "error writing to output file"
msgstr ""
#: panel.cpp:2400
#: panel.cpp:2404
msgid "can't guess output format from file name"
msgstr ""
#: panel.cpp:2405
#: panel.cpp:2409
msgid "programming error; aborting!"
msgstr ""
#: panel.cpp:2417 panel.cpp:2446
#: panel.cpp:2421 panel.cpp:2450
#, c-format
msgid "Unable to begin recording to %s (%s)"
msgstr ""
#: panel.cpp:2474
#: panel.cpp:2478
#, c-format
msgid "Error in audio/video recording (%s); aborting"
msgstr ""
#: panel.cpp:2480
#: panel.cpp:2484
#, c-format
msgid "Error in audio recording (%s); aborting"
msgstr ""
#: panel.cpp:2490
#: panel.cpp:2494
#, c-format
msgid "Error in video recording (%s); aborting"
msgstr ""
@ -1148,16 +1148,26 @@ msgstr ""
msgid "CONTROL"
msgstr ""
#: widgets/sdljoy.cpp:115
#: widgets/sdljoy.cpp:129
#, c-format
msgid "Connected game controller %d"
msgstr ""
#: widgets/sdljoy.cpp:129
#: widgets/sdljoy.cpp:143
#, c-format
msgid "Disconnected game controller %d"
msgstr ""
#: widgets/sdljoy.cpp:229
#, c-format
msgid "Connected joystick %d"
msgstr ""
#: widgets/sdljoy.cpp:246
#, c-format
msgid "Disconnected joystick %d"
msgstr ""
#: xaudio2.cpp:35
msgid "XAudio2: Enumerating devices failed!"
msgstr ""

View File

@ -68,7 +68,18 @@ int wxJoyKeyTextCtrl::DigitalButton(wxSDLJoyEvent& event)
void wxJoyKeyTextCtrl::OnJoy(wxSDLJoyEvent& event)
{
short val = event.GetControlValue();
static wxLongLong last_event = 0;
int val = event.GetControlValue();
int type = event.GetControlType();
// Filter consecutive axis motions within 300ms, as this adds two bindings
// +1/-1 instead of the one intended.
if (type == WXSDLJOY_AXIS && wxGetUTCTimeMillis() - last_event < 300)
return;
last_event = wxGetUTCTimeMillis();
int mod = DigitalButton(event);
int key = event.GetControlIndex(), joy = event.GetJoy() + 1;

View File

@ -1,3 +1,4 @@
#include <cstddef>
#include "wxvbam.h"
#include "wx/sdljoy.h"
#include "SDL.h"
@ -8,6 +9,9 @@
using namespace Range;
// For testing a GameController as a Joystick:
//#define SDL_IsGameController(x) false
DEFINE_EVENT_TYPE(wxEVT_SDLJOY)
wxSDLJoy::wxSDLJoy()
@ -16,8 +20,10 @@ wxSDLJoy::wxSDLJoy()
{
// Start up joystick if not already started
// FIXME: check for errors
SDL_Init(SDL_INIT_JOYSTICK);
SDL_Init(SDL_INIT_GAMECONTROLLER);
SDL_GameControllerEventState(SDL_ENABLE);
SDL_JoystickEventState(SDL_ENABLE);
}
wxSDLJoy::~wxSDLJoy()
@ -49,6 +55,9 @@ void wxSDLJoy::Poll()
{
auto joy = e.cbutton.which;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
auto but = e.cbutton.button;
auto val = e.cbutton.state;
@ -78,6 +87,9 @@ void wxSDLJoy::Poll()
{
auto joy = e.caxis.which;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
auto axis = e.caxis.axis;
auto val = axisval(e.caxis.value);
@ -108,6 +120,9 @@ void wxSDLJoy::Poll()
{
auto joy = e.cdevice.which;
if (!SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) {
DisconnectController(joy);
ConnectController(joy);
@ -133,11 +148,112 @@ void wxSDLJoy::Poll()
break;
}
// Joystck events for non-GameControllers.
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
{
auto joy = e.jbutton.which;
if (SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
auto but = e.jbutton.button;
auto val = e.jbutton.state;
auto prev_val = joystate[joy].button[but];
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy;
ev.ctrl_type = WXSDLJOY_BUTTON;
ev.ctrl_idx = but;
ev.ctrl_val = val;
ev.prev_val = prev_val;
handler->ProcessEvent(ev);
}
joystate[joy].button[but] = val;
wxLogDebug("GOT SDL_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", joy, but, val, prev_val);
}
got_event = true;
break;
}
case SDL_JOYAXISMOTION:
{
auto joy = e.jaxis.which;
if (SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
auto axis = e.jaxis.axis;
auto val = axisval(e.jaxis.value);
auto prev_val = joystate[joy].axis[axis];
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy;
ev.ctrl_type = WXSDLJOY_AXIS;
ev.ctrl_idx = axis;
ev.ctrl_val = val;
ev.prev_val = prev_val;
handler->ProcessEvent(ev);
joystate[joy].axis[axis] = val;
wxLogDebug("GOT SDL_JOYAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy, axis, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_JOYDEVICEADDED:
{
auto joy = e.cdevice.which;
if (SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) {
DisconnectController(joy);
ConnectController(joy);
systemScreenMessage(wxString::Format(_("Connected joystick %d"), joy + 1));
}
got_event = true;
break;
}
case SDL_JOYDEVICEREMOVED:
{
auto joy = e.cdevice.which;
if (SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) {
DisconnectController(joy);
systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), joy + 1));
}
got_event = true;
break;
}
}
}
// I am only doing this shit because hotplug support is fucking broken in SDL right now.
bool do_poll = false;
wxLongLong tm = wxGetUTCTimeMillis();
@ -152,45 +268,90 @@ void wxSDLJoy::Poll()
for (auto&& joy : joystate) {
if (!joy.second.dev) continue;
for (uint8_t but = 0; but < SDL_CONTROLLER_BUTTON_MAX; but++) {
auto last_state = joy.second.button[but];
auto state = SDL_GameControllerGetButton(joy.second.dev, static_cast<SDL_GameControllerButton>(but));
if (SDL_IsGameController(joy.first)) {
for (uint8_t but = 0; but < SDL_CONTROLLER_BUTTON_MAX; but++) {
auto last_state = joy.second.button[but];
auto state = SDL_GameControllerGetButton(joy.second.dev, static_cast<SDL_GameControllerButton>(but));
if (last_state != state) {
if (handler) {
if (last_state != state) {
if (handler) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy.first;
ev.ctrl_type = WXSDLJOY_BUTTON;
ev.ctrl_idx = but;
ev.ctrl_val = state;
ev.prev_val = last_state;
handler->ProcessEvent(ev);
}
joy.second.button[but] = state;
wxLogDebug("POLLED SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", joy.first, but, state, last_state);
}
}
for (uint8_t axis = 0; axis < SDL_CONTROLLER_AXIS_MAX; axis++) {
auto val = axisval(SDL_GameControllerGetAxis(joy.second.dev, static_cast<SDL_GameControllerAxis>(axis)));
auto prev_val = joy.second.axis[axis];
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy.first;
ev.ctrl_type = WXSDLJOY_BUTTON;
ev.ctrl_idx = but;
ev.ctrl_val = state;
ev.prev_val = last_state;
ev.ctrl_type = WXSDLJOY_AXIS;
ev.ctrl_idx = axis;
ev.ctrl_val = val;
ev.prev_val = prev_val;
handler->ProcessEvent(ev);
joy.second.axis[axis] = val;
wxLogDebug("POLLED SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy.first, axis, val, prev_val);
}
joy.second.button[but] = state;
wxLogDebug("POLLED SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", joy.first, but, state, last_state);
}
}
else {
for (uint8_t but = 0; but < SDL_JoystickNumButtons(joy.second.dev); but++) {
auto last_state = joy.second.button[but];
auto state = SDL_JoystickGetButton(joy.second.dev, but);
for (uint8_t axis = 0; axis < SDL_CONTROLLER_AXIS_MAX; axis++) {
auto val = axisval(SDL_GameControllerGetAxis(joy.second.dev, static_cast<SDL_GameControllerAxis>(axis)));
auto prev_val = joy.second.axis[axis];
if (last_state != state) {
if (handler) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy.first;
ev.ctrl_type = WXSDLJOY_BUTTON;
ev.ctrl_idx = but;
ev.ctrl_val = state;
ev.prev_val = last_state;
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy.first;
ev.ctrl_type = WXSDLJOY_AXIS;
ev.ctrl_idx = axis;
ev.ctrl_val = val;
ev.prev_val = prev_val;
handler->ProcessEvent(ev);
}
handler->ProcessEvent(ev);
joy.second.button[but] = state;
joy.second.axis[axis] = val;
wxLogDebug("POLLED SDL_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", joy.first, but, state, last_state);
}
}
wxLogDebug("POLLED SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy.first, axis, val, prev_val);
for (uint8_t axis = 0; axis < SDL_JoystickNumAxes(joy.second.dev); axis++) {
auto val = axisval(SDL_JoystickGetAxis(joy.second.dev, axis));
auto prev_val = joy.second.axis[axis];
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy.first;
ev.ctrl_type = WXSDLJOY_AXIS;
ev.ctrl_idx = axis;
ev.ctrl_val = val;
ev.prev_val = prev_val;
handler->ProcessEvent(ev);
joy.second.axis[axis] = val;
wxLogDebug("POLLED SDL_JOYAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy.first, axis, val, prev_val);
}
}
}
}
@ -199,15 +360,31 @@ void wxSDLJoy::Poll()
void wxSDLJoy::ConnectController(uint8_t joy)
{
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy)))
wxLogDebug("SDL_GameControllerOpen(%d) failed: %s", joy, SDL_GetError());
if (SDL_IsGameController(joy)) {
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy))) {
wxLogDebug("SDL_GameControllerOpen(%d) failed: %s", joy, SDL_GetError());
return;
}
}
else {
if (!(joystate[joy].dev = SDL_JoystickOpen(joy))) {
wxLogDebug("SDL_JoystickOpen(%d) failed: %s", joy, SDL_GetError());
return;
}
}
}
void wxSDLJoy::DisconnectController(uint8_t joy)
{
if (auto& dev = joystate[joy].dev) {
if (SDL_GameControllerGetAttached(dev))
SDL_GameControllerClose(dev);
if (SDL_IsGameController(joy)) {
if (SDL_GameControllerGetAttached(dev))
SDL_GameControllerClose(dev);
}
else {
if (SDL_JoystickGetAttached(dev))
SDL_JoystickClose(dev);
}
dev = nullptr;
}
@ -256,9 +433,9 @@ void wxSDLJoy::SetRumble(bool do_rumble)
rumbling = do_rumble;
#if SDL_VERSION_ATLEAST(2, 0, 9)
// do rumble only on device 0
// Do rumble only on device 0, and only if it's a GameController.
auto dev = joystate[0].dev;
if (dev) {
if (dev && SDL_IsGameController(0)) {
if (rumbling) {
SDL_GameControllerRumble(dev, 0xFFFF, 0xFFFF, 300);
if (!IsRunning())
@ -269,8 +446,6 @@ void wxSDLJoy::SetRumble(bool do_rumble)
Stop();
}
}
else
Stop();
#endif
}
@ -278,3 +453,37 @@ void wxSDLJoy::Notify()
{
SetRumble(rumbling);
}
wxSDLJoyDev::operator SDL_GameController*&()
{
return dev_gc;
}
SDL_GameController*& wxSDLJoyDev::operator=(SDL_GameController* ptr)
{
dev_gc = ptr;
return dev_gc;
}
wxSDLJoyDev::operator SDL_Joystick*&()
{
return dev_js;
}
SDL_Joystick*& wxSDLJoyDev::operator=(SDL_Joystick* ptr)
{
dev_js = ptr;
return dev_js;
}
wxSDLJoyDev::operator bool()
{
return dev_gc != nullptr;
}
std::nullptr_t& wxSDLJoyDev::operator=(std::nullptr_t&& null_ptr)
{
dev_gc = null_ptr;
return null_ptr;
}

View File

@ -9,19 +9,39 @@
//
// The target window will receive EVT_SDLJOY events of type wxSDLJoyEvent.
#include <cstddef>
#include <array>
#include <vector>
#include <unordered_map>
#include <wx/time.h>
#include <wx/event.h>
#include <wx/timer.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include "../common/contains.h"
struct wxSDLJoyDev {
private:
union {
SDL_GameController* dev_gc = nullptr;
SDL_Joystick* dev_js;
};
public:
operator SDL_GameController*&();
SDL_GameController*& operator=(SDL_GameController* ptr);
operator SDL_Joystick*&();
SDL_Joystick*& operator=(SDL_Joystick* ptr);
operator bool();
std::nullptr_t& operator=(std::nullptr_t&& null_ptr);
};
struct wxSDLJoyState {
SDL_GameController* dev = nullptr;
std::array<int16_t, SDL_CONTROLLER_AXIS_MAX> axis{};
std::array<uint8_t, SDL_CONTROLLER_BUTTON_MAX> button{};
wxSDLJoyDev dev;
std::unordered_map<uint8_t, int16_t> axis{};
std::unordered_map<uint8_t, uint8_t> button{};
};
class wxSDLJoy : public wxTimer {