From 09e8da43db5342df092bc537903baefbee46f69c Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Mon, 4 May 2020 22:12:01 +0000 Subject: [PATCH] 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 --- po/wxvbam/wxvbam.pot | 56 +++++--- src/wx/widgets/joyedit.cpp | 13 +- src/wx/widgets/sdljoy.cpp | 281 ++++++++++++++++++++++++++++++++----- src/wx/widgets/wx/sdljoy.h | 26 +++- 4 files changed, 313 insertions(+), 63 deletions(-) diff --git a/po/wxvbam/wxvbam.pot b/po/wxvbam/wxvbam.pot index 55c6e73b..3bc57a52 100644 --- a/po/wxvbam/wxvbam.pot +++ b/po/wxvbam/wxvbam.pot @@ -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 \n" "Language-Team: LANGUAGE \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 "" diff --git a/src/wx/widgets/joyedit.cpp b/src/wx/widgets/joyedit.cpp index 437dbb8a..6376228e 100644 --- a/src/wx/widgets/joyedit.cpp +++ b/src/wx/widgets/joyedit.cpp @@ -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; diff --git a/src/wx/widgets/sdljoy.cpp b/src/wx/widgets/sdljoy.cpp index 1c1a144c..444f3a64 100644 --- a/src/wx/widgets/sdljoy.cpp +++ b/src/wx/widgets/sdljoy.cpp @@ -1,3 +1,4 @@ +#include #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(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(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(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(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; +} diff --git a/src/wx/widgets/wx/sdljoy.h b/src/wx/widgets/wx/sdljoy.h index 82551aa2..0fc7beb6 100644 --- a/src/wx/widgets/wx/sdljoy.h +++ b/src/wx/widgets/wx/sdljoy.h @@ -9,19 +9,39 @@ // // The target window will receive EVT_SDLJOY events of type wxSDLJoyEvent. +#include #include #include #include #include #include #include +#include #include #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 axis{}; - std::array button{}; + wxSDLJoyDev dev; + std::unordered_map axis{}; + std::unordered_map button{}; }; class wxSDLJoy : public wxTimer {