Better joystick hotplug support.

Combine SDL events with polling the axes and buttons of controllers
every 10 milliseconds, this is necessary because of this bug in SDL:

https://bugzilla.libsdl.org/show_bug.cgi?id=4886

which causes SDL to not generation button and axes events after a
joystick is disconnected and then reconnected to the system.

Change the axes state from a unordered_map to an std::array because the
number of axes is fixed, add analogous buttons array.

Keep controller state synchronized between the event receiving code and
the polling code.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
This commit is contained in:
Rafael Kitover 2019-12-22 03:17:33 +00:00
parent f306cc63b0
commit 0577f8afc2
No known key found for this signature in database
GPG Key ID: 08AB596679D86240
2 changed files with 86 additions and 5 deletions

View File

@ -2,7 +2,6 @@
#include "wx/sdljoy.h"
#include "SDL.h"
#include <SDL_events.h>
#include <SDL_gamecontroller.h>
#include <wx/window.h>
#include "../common/range.hpp"
#include "../common/contains.h"
@ -26,7 +25,7 @@ wxSDLJoy::~wxSDLJoy()
// SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
static int axisval(int x)
static int16_t axisval(int16_t x)
{
if (x > 0x1fff)
return 1;
@ -41,6 +40,8 @@ void wxSDLJoy::Poll()
wxEvtHandler* handler = evthandler ? evthandler : wxWindow::FindFocus();
SDL_Event e;
bool got_event = false;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_CONTROLLERBUTTONDOWN:
@ -51,9 +52,9 @@ void wxSDLJoy::Poll()
if (contains(joystate, joy)) {
auto but = e.cbutton.button;
auto val = e.cbutton.state;
auto prev_val = !val;
auto prev_val = joystate[joy].button[but];
if (handler) {
if (handler && val != prev_val) {
wxSDLJoyEvent ev(wxEVT_SDLJOY);
ev.joy = joy;
ev.ctrl_type = WXSDLJOY_BUTTON;
@ -64,8 +65,13 @@ void wxSDLJoy::Poll()
handler->ProcessEvent(ev);
}
joystate[joy].button[but] = val;
wxLogDebug("GOT SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", joy, but, val, prev_val);
}
got_event = true;
break;
}
case SDL_CONTROLLERAXISMOTION:
@ -92,6 +98,9 @@ void wxSDLJoy::Poll()
wxLogDebug("GOT SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy, axis, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_CONTROLLERDEVICEADDED:
@ -105,6 +114,9 @@ void wxSDLJoy::Poll()
systemScreenMessage(wxString::Format(_("Connected game controller %d"), joy + 1));
}
got_event = true;
break;
}
case SDL_CONTROLLERDEVICEREMOVED:
@ -116,10 +128,71 @@ void wxSDLJoy::Poll()
systemScreenMessage(wxString::Format(_("Disconnected game controller %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();
if (got_event)
last_poll = tm;
else if (tm - last_poll > POLL_TIME_MS) {
do_poll = true;
last_poll = tm;
}
if (do_poll) {
for (auto&& joy : joystate) {
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) {
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_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);
}
}
}
}
}
void wxSDLJoy::ConnectController(uint8_t joy)

View File

@ -9,8 +9,10 @@
//
// The target window will receive EVT_SDLJOY events of type wxSDLJoyEvent.
#include <array>
#include <vector>
#include <unordered_map>
#include <wx/time.h>
#include <wx/event.h>
#include <wx/timer.h>
#include <SDL_gamecontroller.h>
@ -18,7 +20,8 @@
struct wxSDLJoyState {
SDL_GameController* dev = nullptr;
std::unordered_map<uint8_t, int8_t> axis;
std::array<int16_t, SDL_CONTROLLER_AXIS_MAX> axis{};
std::array<uint8_t, SDL_CONTROLLER_BUTTON_MAX> button{};
};
class wxSDLJoy : public wxTimer {
@ -52,10 +55,15 @@ protected:
void Notify();
void ConnectController(uint8_t joy);
void DisconnectController(uint8_t joy);
const uint8_t POLL_TIME_MS = 10;
private:
std::unordered_map<uint8_t, wxSDLJoyState> joystate;
wxEvtHandler* evthandler;
bool add_all = false, rumbling = false;
wxLongLong last_poll = wxGetUTCTimeMillis();
};
enum {