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

View File

@ -68,7 +68,18 @@ int wxJoyKeyTextCtrl::DigitalButton(wxSDLJoyEvent& event)
void wxJoyKeyTextCtrl::OnJoy(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 mod = DigitalButton(event);
int key = event.GetControlIndex(), joy = event.GetJoy() + 1; int key = event.GetControlIndex(), joy = event.GetJoy() + 1;

View File

@ -1,3 +1,4 @@
#include <cstddef>
#include "wxvbam.h" #include "wxvbam.h"
#include "wx/sdljoy.h" #include "wx/sdljoy.h"
#include "SDL.h" #include "SDL.h"
@ -8,6 +9,9 @@
using namespace Range; using namespace Range;
// For testing a GameController as a Joystick:
//#define SDL_IsGameController(x) false
DEFINE_EVENT_TYPE(wxEVT_SDLJOY) DEFINE_EVENT_TYPE(wxEVT_SDLJOY)
wxSDLJoy::wxSDLJoy() wxSDLJoy::wxSDLJoy()
@ -16,8 +20,10 @@ wxSDLJoy::wxSDLJoy()
{ {
// Start up joystick if not already started // Start up joystick if not already started
// FIXME: check for errors // FIXME: check for errors
SDL_Init(SDL_INIT_JOYSTICK);
SDL_Init(SDL_INIT_GAMECONTROLLER); SDL_Init(SDL_INIT_GAMECONTROLLER);
SDL_GameControllerEventState(SDL_ENABLE); SDL_GameControllerEventState(SDL_ENABLE);
SDL_JoystickEventState(SDL_ENABLE);
} }
wxSDLJoy::~wxSDLJoy() wxSDLJoy::~wxSDLJoy()
@ -49,6 +55,9 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.cbutton.which; auto joy = e.cbutton.which;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) { if (contains(joystate, joy)) {
auto but = e.cbutton.button; auto but = e.cbutton.button;
auto val = e.cbutton.state; auto val = e.cbutton.state;
@ -78,6 +87,9 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.caxis.which; auto joy = e.caxis.which;
if (!SDL_IsGameController(joy))
break;
if (contains(joystate, joy)) { if (contains(joystate, joy)) {
auto axis = e.caxis.axis; auto axis = e.caxis.axis;
auto val = axisval(e.caxis.value); auto val = axisval(e.caxis.value);
@ -108,6 +120,9 @@ void wxSDLJoy::Poll()
{ {
auto joy = e.cdevice.which; auto joy = e.cdevice.which;
if (!SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) { if (add_all || contains(joystate, joy)) {
DisconnectController(joy); DisconnectController(joy);
ConnectController(joy); ConnectController(joy);
@ -133,10 +148,111 @@ void wxSDLJoy::Poll()
break; 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);
} }
} }
// I am only doing this shit because hotplug support is fucking broken in SDL right now. 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;
}
}
}
bool do_poll = false; bool do_poll = false;
wxLongLong tm = wxGetUTCTimeMillis(); wxLongLong tm = wxGetUTCTimeMillis();
@ -152,6 +268,7 @@ void wxSDLJoy::Poll()
for (auto&& joy : joystate) { for (auto&& joy : joystate) {
if (!joy.second.dev) continue; if (!joy.second.dev) continue;
if (SDL_IsGameController(joy.first)) {
for (uint8_t but = 0; but < SDL_CONTROLLER_BUTTON_MAX; but++) { for (uint8_t but = 0; but < SDL_CONTROLLER_BUTTON_MAX; but++) {
auto last_state = joy.second.button[but]; auto last_state = joy.second.button[but];
auto state = SDL_GameControllerGetButton(joy.second.dev, static_cast<SDL_GameControllerButton>(but)); auto state = SDL_GameControllerGetButton(joy.second.dev, static_cast<SDL_GameControllerButton>(but));
@ -194,20 +311,80 @@ void wxSDLJoy::Poll()
} }
} }
} }
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);
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_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", joy.first, but, state, last_state);
}
}
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);
}
}
}
}
} }
} }
void wxSDLJoy::ConnectController(uint8_t joy) void wxSDLJoy::ConnectController(uint8_t joy)
{ {
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy))) if (SDL_IsGameController(joy)) {
if (!(joystate[joy].dev = SDL_GameControllerOpen(joy))) {
wxLogDebug("SDL_GameControllerOpen(%d) failed: %s", joy, SDL_GetError()); 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) void wxSDLJoy::DisconnectController(uint8_t joy)
{ {
if (auto& dev = joystate[joy].dev) { if (auto& dev = joystate[joy].dev) {
if (SDL_IsGameController(joy)) {
if (SDL_GameControllerGetAttached(dev)) if (SDL_GameControllerGetAttached(dev))
SDL_GameControllerClose(dev); SDL_GameControllerClose(dev);
}
else {
if (SDL_JoystickGetAttached(dev))
SDL_JoystickClose(dev);
}
dev = nullptr; dev = nullptr;
} }
@ -256,9 +433,9 @@ void wxSDLJoy::SetRumble(bool do_rumble)
rumbling = do_rumble; rumbling = do_rumble;
#if SDL_VERSION_ATLEAST(2, 0, 9) #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; auto dev = joystate[0].dev;
if (dev) { if (dev && SDL_IsGameController(0)) {
if (rumbling) { if (rumbling) {
SDL_GameControllerRumble(dev, 0xFFFF, 0xFFFF, 300); SDL_GameControllerRumble(dev, 0xFFFF, 0xFFFF, 300);
if (!IsRunning()) if (!IsRunning())
@ -269,8 +446,6 @@ void wxSDLJoy::SetRumble(bool do_rumble)
Stop(); Stop();
} }
} }
else
Stop();
#endif #endif
} }
@ -278,3 +453,37 @@ void wxSDLJoy::Notify()
{ {
SetRumble(rumbling); 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. // The target window will receive EVT_SDLJOY events of type wxSDLJoyEvent.
#include <cstddef>
#include <array> #include <array>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <wx/time.h> #include <wx/time.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/timer.h> #include <wx/timer.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h> #include <SDL_gamecontroller.h>
#include "../common/contains.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 { struct wxSDLJoyState {
SDL_GameController* dev = nullptr; wxSDLJoyDev dev;
std::array<int16_t, SDL_CONTROLLER_AXIS_MAX> axis{}; std::unordered_map<uint8_t, int16_t> axis{};
std::array<uint8_t, SDL_CONTROLLER_BUTTON_MAX> button{}; std::unordered_map<uint8_t, uint8_t> button{};
}; };
class wxSDLJoy : public wxTimer { class wxSDLJoy : public wxTimer {