Refactor wxSDLJoy

This is a major refactor of the wxSDLJoy class.
* Move handling of SDL objects creation and destruction to its own
  class. This simplifies the lifespan of SDL-related objects.
* Re-add handling of HATs. This is necessary for DirectInput
  controllers.
* Rename the public API for wxSDLJoy to be clearer.
* Add documentation for every class related to wxSDLJoy.
This commit is contained in:
Fabrice de Gans-Riberi 2020-09-05 21:56:38 -07:00 committed by Rafael Kitover
parent a00f258588
commit edc34942a8
8 changed files with 585 additions and 582 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-09-05 19:36+0000\n" "POT-Creation-Date: 2020-09-05 21:42-0700\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"
@ -1156,17 +1156,7 @@ msgstr ""
msgid "CONTROL" msgid "CONTROL"
msgstr "" msgstr ""
#: widgets/sdljoy.cpp:122 #: widgets/sdljoy.cpp:107
#, c-format
msgid "Connected game controller %d: %s"
msgstr ""
#: widgets/sdljoy.cpp:137
#, c-format
msgid "Disconnected game controller %d"
msgstr ""
#: widgets/sdljoy.cpp:214
#, c-format #, c-format
msgid "Connected joystick %d: %s" msgid "Connected joystick %d: %s"
msgstr "" msgstr ""

View File

@ -2736,7 +2736,7 @@ EVT_HANDLER(EmulatorDirectories, "Directories...")
EVT_HANDLER(JoypadConfigure, "Joypad options...") EVT_HANDLER(JoypadConfigure, "Joypad options...")
{ {
wxDialog* dlg = GetXRCDialog("JoypadConfig"); wxDialog* dlg = GetXRCDialog("JoypadConfig");
joy.Add(); joy.PollAllJoysticks();
auto frame = wxGetApp().frame; auto frame = wxGetApp().frame;
bool joy_timer = frame->IsJoyPollTimerRunning(); bool joy_timer = frame->IsJoyPollTimerRunning();
@ -2754,7 +2754,7 @@ EVT_HANDLER(JoypadConfigure, "Joypad options...")
EVT_HANDLER(Customize, "Customize UI...") EVT_HANDLER(Customize, "Customize UI...")
{ {
wxDialog* dlg = GetXRCDialog("AccelConfig"); wxDialog* dlg = GetXRCDialog("AccelConfig");
joy.Add(); joy.PollAllJoysticks();
auto frame = wxGetApp().frame; auto frame = wxGetApp().frame;
bool joy_timer = frame->IsJoyPollTimerRunning(); bool joy_timer = frame->IsJoyPollTimerRunning();

View File

@ -2582,12 +2582,14 @@ void MainFrame::set_global_accels()
// the menus will be added now // the menus will be added now
// first, zero out menu item on all accels // first, zero out menu item on all accels
std::unordered_set<unsigned> needed_joysticks;
for (size_t i = 0; i < accels.size(); ++i) { for (size_t i = 0; i < accels.size(); ++i) {
accels[i].Set(accels[i].GetUkey(), accels[i].GetJoystick(), accels[i].GetFlags(), accels[i].GetKeyCode(), accels[i].GetCommand()); accels[i].Set(accels[i].GetUkey(), accels[i].GetJoystick(), accels[i].GetFlags(), accels[i].GetKeyCode(), accels[i].GetCommand());
if (accels[i].GetJoystick()) { if (accels[i].GetJoystick()) {
joy.Add(accels[i].GetJoystick() - 1); needed_joysticks.insert(accels[i].GetJoystick());
} }
} }
joy.PollJoysticks(needed_joysticks);
// yet another O(n*m) loop. I really ought to sort the accel arrays // yet another O(n*m) loop. I really ought to sort the accel arrays
for (int i = 0; i < ncmds; i++) { for (int i = 0; i < ncmds; i++) {

View File

@ -1391,25 +1391,25 @@ void GameArea::OnSize(wxSizeEvent& ev)
void GameArea::OnSDLJoy(wxSDLJoyEvent& ev) void GameArea::OnSDLJoy(wxSDLJoyEvent& ev)
{ {
int key = ev.GetControlIndex(); int key = ev.control_index();
int mod = wxJoyKeyTextCtrl::DigitalButton(ev); int mod = wxJoyKeyTextCtrl::DigitalButton(ev);
int joy = ev.GetJoy() + 1; int joy = ev.player_index();
// mutually exclusive key types unpress their opposite // mutually exclusive key types unpress their opposite
if (mod == WXJB_AXIS_PLUS) { if (mod == WXJB_AXIS_PLUS) {
process_key_press(false, key, WXJB_AXIS_MINUS, joy); process_key_press(false, key, WXJB_AXIS_MINUS, joy);
process_key_press(ev.GetControlValue() != 0, key, mod, joy); process_key_press(ev.control_value() != 0, key, mod, joy);
} else if (mod == WXJB_AXIS_MINUS) { } else if (mod == WXJB_AXIS_MINUS) {
process_key_press(false, key, WXJB_AXIS_PLUS, joy); process_key_press(false, key, WXJB_AXIS_PLUS, joy);
process_key_press(ev.GetControlValue() != 0, key, mod, joy); process_key_press(ev.control_value() != 0, key, mod, joy);
} else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST) { } else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST) {
int value = ev.GetControlValue(); int value = ev.control_value();
process_key_press(value & SDL_HAT_UP, key, WXJB_HAT_N, joy); process_key_press(value & SDL_HAT_UP, key, WXJB_HAT_N, joy);
process_key_press(value & SDL_HAT_DOWN, key, WXJB_HAT_S, joy); process_key_press(value & SDL_HAT_DOWN, key, WXJB_HAT_S, joy);
process_key_press(value & SDL_HAT_RIGHT, key, WXJB_HAT_E, joy); process_key_press(value & SDL_HAT_RIGHT, key, WXJB_HAT_E, joy);
process_key_press(value & SDL_HAT_LEFT, key, WXJB_HAT_W, joy); process_key_press(value & SDL_HAT_LEFT, key, WXJB_HAT_W, joy);
} else } else
process_key_press(ev.GetControlValue() != 0, key, mod, joy); process_key_press(ev.control_value() != 0, key, mod, joy);
// tell Linux to turn off the screensaver/screen-blank if joystick button was pressed // tell Linux to turn off the screensaver/screen-blank if joystick button was pressed
// this shouldn't be necessary of course // this shouldn't be necessary of course

View File

@ -19,8 +19,8 @@ wxJoyKeyBinding newWxJoyKeyBinding(int key, int mod, int joy)
int wxJoyKeyTextCtrl::DigitalButton(wxSDLJoyEvent& event) int wxJoyKeyTextCtrl::DigitalButton(wxSDLJoyEvent& event)
{ {
int sdlval = event.GetControlValue(); int16_t sdlval = event.control_value();
int sdltype = event.GetControlType(); wxSDLControl sdltype = event.control();
switch (sdltype) { switch (sdltype) {
case WXSDLJOY_AXIS: case WXSDLJOY_AXIS:
@ -72,8 +72,8 @@ void wxJoyKeyTextCtrl::OnJoy(wxSDLJoyEvent& event)
{ {
static wxLongLong last_event = 0; static wxLongLong last_event = 0;
int val = event.GetControlValue(); int16_t val = event.control_value();
int type = event.GetControlType(); wxSDLControl type = event.control();
// Filter consecutive axis motions within 300ms, as this adds two bindings // Filter consecutive axis motions within 300ms, as this adds two bindings
// +1/-1 instead of the one intended. // +1/-1 instead of the one intended.
@ -83,7 +83,8 @@ void wxJoyKeyTextCtrl::OnJoy(wxSDLJoyEvent& event)
last_event = wxGetUTCTimeMillis(); last_event = wxGetUTCTimeMillis();
int mod = DigitalButton(event); int mod = DigitalButton(event);
int key = event.GetControlIndex(), joy = event.GetJoy() + 1; uint8_t key = event.control_index();
unsigned joy = event.player_index();
if (!val || mod < 0) if (!val || mod < 0)
return; return;

View File

@ -1,35 +1,36 @@
#include <cstddef>
#include "wxvbam.h"
#include "wx/sdljoy.h" #include "wx/sdljoy.h"
#include "SDL.h"
#include <SDL_events.h> #include <algorithm>
#include "../common/range.hpp" #include <wx/timer.h>
#include <SDL.h>
#include "../common/contains.h" #include "../common/contains.h"
#include "../wxvbam.h"
using namespace Range; namespace {
// For testing a GameController as a Joystick: const std::string SDLEventTypeToDebugString(int32_t event_type) {
//#define SDL_IsGameController(x) false switch (event_type) {
case SDL_CONTROLLERBUTTONDOWN:
DEFINE_EVENT_TYPE(wxEVT_SDLJOY) case SDL_CONTROLLERBUTTONUP:
return "SDL_CONTROLLERBUTTON";
wxSDLJoy::wxSDLJoy() case SDL_CONTROLLERAXISMOTION:
: wxTimer() return "SDL_CONTROLLERAXISMOTION";
{ case SDL_JOYBUTTONDOWN:
// Start up joystick if not already started case SDL_JOYBUTTONUP:
// FIXME: check for errors return "SDL_JOYBUTTON";
SDL_Init(SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER); case SDL_JOYAXISMOTION:
SDL_GameControllerEventState(SDL_ENABLE); return "SDL_JOYAXISMOTION";
SDL_JoystickEventState(SDL_ENABLE); case SDL_JOYHATMOTION:
return "SDL_JOYHATMOTION";
default:
assert(false);
return "UNKNOWN SDL EVENT";
}
} }
wxSDLJoy::~wxSDLJoy() // Converts an axis value to a direction since we do not need analog controls.
{ static int16_t AxisValueToDirection(int16_t x) {
// SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
static int16_t axisval(int16_t x)
{
if (x > 0x1fff) if (x > 0x1fff)
return 1; return 1;
else if (x < -0x1fff) else if (x < -0x1fff)
@ -38,420 +39,460 @@ static int16_t axisval(int16_t x)
return 0; return 0;
} }
void wxSDLJoy::CreateAndSendEvent(unsigned short joy, unsigned short ctrl_type, unsigned short ctrl_idx, short ctrl_val, short prev_val) // The interval between 2 polls in ms.
{ const wxLongLong kPollTimeInterval(25);
auto handler = wxGetApp().frame->GetJoyEventHandler();
if (!handler) } // namespace
// For testing a GameController as a Joystick:
//#define SDL_IsGameController(x) false
DEFINE_EVENT_TYPE(wxEVT_SDLJOY)
wxSDLJoyEvent::wxSDLJoyEvent(
unsigned player_index,
wxSDLControl control,
uint8_t control_index,
int16_t control_value) :
wxCommandEvent(wxEVT_SDLJOY),
player_index_(player_index),
control_(control),
control_index_(control_index),
control_value_(control_value) {}
// Represents the current state of a joystick. This class takes care of
// initializing and destroying SDL resources on construction and destruction so
// every associated SDL state for a joystick dies with this object.
class wxSDLJoyState : public wxTimer {
public:
explicit wxSDLJoyState(int sdl_index);
~wxSDLJoyState() override;
// Disable copy constructor and assignment. This is to prevent double
// closure of the SDL objects.
wxSDLJoyState(const wxSDLJoyState&) = delete;
wxSDLJoyState& operator=(const wxSDLJoyState&) = delete;
// Returns true if this object was properly initialized.
bool IsValid() const;
// Processes an SDL event.
void ProcessEvent(int32_t event_type,
uint8_t control_index,
int16_t control_value);
// Polls the current state of the joystick and sends events as needed.
void Poll();
// Activates or deactivates rumble.
void SetRumble(bool activate_rumble);
SDL_JoystickID joystick_id() const { return joystick_id_; }
private:
// wxTimer implementation.
// Used to rumble on a timer.
void Notify() override;
// The Joystick player index.
unsigned player_index_;
// SDL Joystick ID used for events.
SDL_JoystickID joystick_id_;
// The SDL GameController instance.
SDL_GameController* game_controller_ = nullptr;
// The SDL Joystick instance.
SDL_Joystick* joystick_ = nullptr;
// Current state of Joystick axis.
std::unordered_map<uint8_t, int16_t> axis_{};
// Current state of Joystick buttons.
std::unordered_map<uint8_t, uint8_t> buttons_{};
// Current state of Joystick HAT. Unused for GameControllers.
std::unordered_map<uint8_t, uint8_t> hats_{};
// Set to true to activate joystick rumble.
bool rumbling_ = false;
};
wxSDLJoyState::wxSDLJoyState(int sdl_index)
: player_index_(sdl_index + 1) {
if (SDL_IsGameController(sdl_index)) {
game_controller_ = SDL_GameControllerOpen(sdl_index);
if (game_controller_)
joystick_ = SDL_GameControllerGetJoystick(game_controller_);
} else {
joystick_ = SDL_JoystickOpen(sdl_index);
}
if (!joystick_)
return; return;
wxSDLJoyEvent *ev = new wxSDLJoyEvent(wxEVT_SDLJOY); joystick_id_ = SDL_JoystickInstanceID(joystick_);
ev->joy = joy; systemScreenMessage(
ev->ctrl_type = ctrl_type; wxString::Format(_("Connected joystick %d: %s"),
ev->ctrl_idx = ctrl_idx; player_index_, SDL_JoystickNameForIndex(sdl_index)));
ev->ctrl_val = ctrl_val;
ev->prev_val = prev_val;
wxQueueEvent(handler, ev);
} }
void wxSDLJoy::Poll() wxSDLJoyState::~wxSDLJoyState() {
{ // Nothing to do if this object is not initialized.
SDL_Event e; if (!joystick_)
return;
bool got_event = false;
if (game_controller_)
while (SDL_PollEvent(&e)) { SDL_GameControllerClose(game_controller_);
switch (e.type) { else
case SDL_CONTROLLERBUTTONDOWN: SDL_JoystickClose(joystick_);
case SDL_CONTROLLERBUTTONUP:
{ systemScreenMessage(
auto joy = e.cbutton.which; wxString::Format(_("Disconnected joystick %d"), player_index_));
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
auto but = e.cbutton.button;
auto val = e.cbutton.state;
auto prev_val = state.button[but];
if (val != prev_val) {
CreateAndSendEvent(state.index, WXSDLJOY_BUTTON, but, val, prev_val);
state.button[but] = val;
wxLogDebug("GOT SDL_CONTROLLERBUTTON: joy:%d but:%d val:%d prev_val:%d", state.index, but, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_CONTROLLERAXISMOTION:
{
auto joy = e.caxis.which;
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
auto axis = e.caxis.axis;
auto val = axisval(e.caxis.value);
auto prev_val = state.axis[axis];
if (val != prev_val) {
CreateAndSendEvent(state.index, WXSDLJOY_AXIS, axis, val, prev_val);
state.axis[axis] = val;
wxLogDebug("GOT SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", state.index, axis, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_CONTROLLERDEVICEADDED:
{
auto joy = e.cdevice.which;
if (add_all || contains(joystate, joy)) {
RemapControllers();
auto& state = joystate[joy];
if (state.dev)
systemScreenMessage(wxString::Format(_("Connected game controller %d: %s"), joy, SDL_GameControllerName(state.dev)));
}
got_event = true;
break;
}
case SDL_CONTROLLERDEVICEREMOVED:
{
auto joy = e.cdevice.which;
if (contains(instance_map, joy)) {
auto index = instance_map[joy]->index;
RemapControllers();
systemScreenMessage(wxString::Format(_("Disconnected game controller %d"), index));
}
got_event = true;
break;
}
// Joystck events for non-GameControllers.
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
{
auto joy = e.jbutton.which;
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
auto but = e.jbutton.button;
auto val = e.jbutton.state;
auto prev_val = state.button[but];
if (val != prev_val) {
CreateAndSendEvent(state.index, WXSDLJOY_BUTTON, but, val, prev_val);
state.button[but] = val;
wxLogDebug("GOT SDL_JOYBUTTON: joy:%d but:%d val:%d prev_val:%d", state.index, but, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_JOYAXISMOTION:
{
auto joy = e.jaxis.which;
if (contains(instance_map, joy)) {
auto& state = *(instance_map[joy]);
if (SDL_IsGameController(state.index))
break;
auto axis = e.jaxis.axis;
auto val = axisval(e.jaxis.value);
auto prev_val = state.axis[axis];
if (val != prev_val) {
CreateAndSendEvent(state.index, WXSDLJOY_AXIS, axis, val, prev_val);
state.axis[axis] = val;
wxLogDebug("GOT SDL_JOYAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", state.index, axis, val, prev_val);
}
}
got_event = true;
break;
}
case SDL_JOYDEVICEADDED:
{
auto joy = e.jdevice.which;
if (SDL_IsGameController(joy))
break;
if (add_all || contains(joystate, joy)) {
RemapControllers();
auto& state = joystate[joy];
if (state.dev)
systemScreenMessage(wxString::Format(_("Connected joystick %d: %s"), joy, SDL_JoystickName(state.dev)));
}
got_event = true;
break;
}
case SDL_JOYDEVICEREMOVED:
{
auto joy = e.jdevice.which;
if (contains(instance_map, joy)) {
auto index = instance_map[joy]->index;
RemapControllers();
systemScreenMessage(wxString::Format(_("Disconnected joystick %d"), index));
}
got_event = true;
break;
}
}
}
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) {
if (!joy.second.dev) continue;
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) {
CreateAndSendEvent(joy.first, WXSDLJOY_BUTTON, but, state, last_state);
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 (val != prev_val) {
CreateAndSendEvent(joy.first, WXSDLJOY_AXIS, axis, val, prev_val);
joy.second.axis[axis] = val;
wxLogDebug("POLLED SDL_CONTROLLERAXISMOTION: joy:%d axis:%d val:%d prev_val:%d", joy.first, axis, val, prev_val);
}
}
}
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) {
CreateAndSendEvent(joy.first, WXSDLJOY_BUTTON, but, state, last_state);
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 (val != prev_val) {
CreateAndSendEvent(joy.first, WXSDLJOY_AXIS, axis, val, prev_val);
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) bool wxSDLJoyState::IsValid() const {
{ return joystick_;
SDL_Joystick* js_dev = nullptr;
bool is_gc;
if ((is_gc = SDL_IsGameController(joy))) {
auto dev = SDL_GameControllerOpen(joy);
if (dev) {
joystate[joy].dev = dev;
js_dev = SDL_GameControllerGetJoystick(dev);
}
}
else {
if ((js_dev = SDL_JoystickOpen(joy)))
joystate[joy].dev = js_dev;
}
if (js_dev) {
auto instance = SDL_JoystickInstanceID(js_dev);
instance_map[instance] = &(joystate[joy]);
joystate[joy].instance = instance;
}
joystate[joy].index = joy;
joystate[joy].is_gc = is_gc;
} }
void wxSDLJoy::RemapControllers() void wxSDLJoyState::ProcessEvent(int32_t event_type,
{ uint8_t control_index,
for (auto&& joy : joystate) { int16_t control_value) {
auto& state = joy.second; int16_t previous_value = 0;
wxSDLControl control;
bool value_changed = false;
DisconnectController(state); switch (event_type) {
ConnectController(joy.first); case SDL_JOYBUTTONDOWN:
} case SDL_JOYBUTTONUP:
} // Do not process joystick events for game controllers.
if (game_controller_) {
void wxSDLJoy::DisconnectController(wxSDLJoyState& state) return;
{
if (auto& dev = state.dev) {
if (state.is_gc) {
SDL_GameControllerClose(dev);
} }
else { // Fallhrough.
SDL_JoystickClose(dev); case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
control = wxSDLControl::WXSDLJOY_BUTTON;
previous_value = buttons_[control_index];
if (previous_value != control_value) {
buttons_[control_index] = control_value;
value_changed = true;
} }
break;
dev = nullptr; case SDL_JOYHATMOTION:
} // Do not process joystick events for game controllers.
if (game_controller_) {
return;
}
control = wxSDLControl::WXSDLJOY_HAT;
previous_value = hats_[control_index];
if (previous_value != control_value) {
hats_[control_index] = control_value;
value_changed = true;
}
break;
instance_map.erase(state.instance); case SDL_JOYAXISMOTION:
} // Do not process joystick events for game controllers.
if (game_controller_) {
void wxSDLJoy::Add(int8_t joy_n) return;
{ }
if (joy_n < 0) { // Fallhrough.
for (uint8_t joy : range(0, SDL_NumJoysticks())) case SDL_CONTROLLERAXISMOTION:
ConnectController(joy); control = wxSDLControl::WXSDLJOY_AXIS;
previous_value = axis_[control_index];
add_all = true; if (previous_value != control_value) {
axis_[control_index] = control_value;
value_changed = true;
}
break;
default:
// This should never happen.
assert(false);
return; return;
} }
ConnectController(joy_n); if (value_changed) {
} wxLogDebug("GOT %s: joy:%d ctrl_idx:%d val:%d prev_val:%d",
SDLEventTypeToDebugString(event_type), player_index_,
control_index, control_value, previous_value);
void wxSDLJoy::Remove(int8_t joy_n) auto handler = wxGetApp().frame->GetJoyEventHandler();
{ if (!handler)
add_all = false; return;
if (joy_n < 0) { wxQueueEvent(handler,
for (auto&& joy : joystate) new wxSDLJoyEvent(
DisconnectController(joy.second); player_index_, control, control_index, control_value));
joystate.clear();
return;
} }
DisconnectController(joystate[joy_n]);
joystate.erase(joy_n);
} }
void wxSDLJoy::SetRumble(bool do_rumble) void wxSDLJoyState::Poll() {
{ if (game_controller_) {
rumbling = do_rumble; for (uint8_t but = 0; but < SDL_CONTROLLER_BUTTON_MAX; but++) {
uint16_t previous_value = buttons_[but];
uint16_t current_value =
SDL_GameControllerGetButton(
game_controller_,
static_cast<SDL_GameControllerButton>(but));
if (previous_value != current_value)
ProcessEvent(SDL_CONTROLLERBUTTONUP, but, current_value);
}
for (uint8_t axis = 0; axis < SDL_CONTROLLER_AXIS_MAX; axis++) {
uint16_t previous_value = axis_[axis];
uint16_t current_value =
AxisValueToDirection(
SDL_GameControllerGetAxis(
game_controller_,
static_cast<SDL_GameControllerAxis>(axis)));
if (previous_value != current_value)
ProcessEvent(SDL_CONTROLLERAXISMOTION, axis, current_value);
}
} else {
for (uint8_t but = 0; but < SDL_JoystickNumButtons(joystick_); but++) {
uint16_t previous_value = buttons_[but];
uint16_t current_value = SDL_JoystickGetButton(joystick_, but);
if (previous_value != current_value)
ProcessEvent(SDL_JOYBUTTONUP, but, current_value);
}
for (uint8_t axis = 0; axis < SDL_JoystickNumAxes(joystick_); axis++) {
uint16_t previous_value = axis_[axis];
uint16_t current_value =
AxisValueToDirection(SDL_JoystickGetButton(joystick_, axis));
if (previous_value != current_value)
ProcessEvent(SDL_JOYAXISMOTION, axis, current_value);
}
for (uint8_t hat = 0; hat < SDL_JoystickNumHats(joystick_); hat++) {
uint16_t previous_value = hats_[hat];
uint16_t current_value = SDL_JoystickGetHat(joystick_, hat);
if (previous_value != current_value)
ProcessEvent(SDL_JOYHATMOTION, hat, current_value);
}
}
}
void wxSDLJoyState::SetRumble(bool activate_rumble) {
rumbling_ = activate_rumble;
#if SDL_VERSION_ATLEAST(2, 0, 9) #if SDL_VERSION_ATLEAST(2, 0, 9)
// Do rumble only on device 0, and only if it's a GameController. if (!game_controller_)
auto dev = joystate[0].dev; return;
if (dev && SDL_IsGameController(0)) {
if (rumbling) { if (rumbling_) {
SDL_GameControllerRumble(dev, 0xFFFF, 0xFFFF, 300); SDL_GameControllerRumble(game_controller_, 0xFFFF, 0xFFFF, 300);
if (!IsRunning()) if (!IsRunning())
Start(150); Start(150);
} } else {
else { SDL_GameControllerRumble(game_controller_, 0, 0, 0);
SDL_GameControllerRumble(dev, 0, 0, 0); Stop();
Stop();
}
} }
#endif #endif
} }
void wxSDLJoy::Notify() void wxSDLJoyState::Notify() {
{ SetRumble(rumbling_);
SetRumble(rumbling);
} }
wxSDLJoyDev::operator SDL_GameController*&() wxSDLJoy::wxSDLJoy() {
{ // Start up joystick if not already started
return dev_gc; // FIXME: check for errors
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
SDL_GameControllerEventState(SDL_ENABLE);
SDL_JoystickEventState(SDL_ENABLE);
} }
SDL_GameController*& wxSDLJoyDev::operator=(SDL_GameController* ptr) wxSDLJoy::~wxSDLJoy() {
{ // It is necessary to free all SDL resources before quitting SDL.
dev_gc = ptr; joystick_states_.clear();
return dev_gc; SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
} }
void wxSDLJoy::Poll() {
SDL_Event e;
bool got_event = false;
wxSDLJoyDev::operator SDL_Joystick*&() while (SDL_PollEvent(&e)) {
{ switch (e.type) {
return dev_js; case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
{
wxSDLJoyState* joy_state = FindJoyState(e.cbutton.which);
if (joy_state) {
joy_state->ProcessEvent(
e.type, e.cbutton.button, e.cbutton.state);
}
got_event = true;
break;
}
case SDL_CONTROLLERAXISMOTION:
{
wxSDLJoyState* joy_state = FindJoyState(e.caxis.which);
if (joy_state) {
joy_state->ProcessEvent(
e.type, e.caxis.axis, AxisValueToDirection(e.caxis.value));
}
got_event = true;
break;
}
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
// Do nothing. This will be handled with JOYDEVICEADDED and
// JOYDEVICEREMOVED events.
break;
// Joystick events for non-GameControllers.
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
{
wxSDLJoyState* joy_state = FindJoyState(e.jbutton.which);
if (joy_state) {
joy_state->ProcessEvent(
e.type, e.jbutton.button, e.jbutton.state);
}
got_event = true;
break;
}
case SDL_JOYAXISMOTION:
{
wxSDLJoyState* joy_state = FindJoyState(e.jaxis.which);
if (joy_state) {
joy_state->ProcessEvent(
e.type, e.jaxis.axis, AxisValueToDirection(e.jaxis.value));
}
got_event = true;
break;
}
case SDL_JOYHATMOTION:
{
wxSDLJoyState* joy_state = FindJoyState(e.jhat.which);
if (joy_state) {
joy_state->ProcessEvent(
e.type, e.jhat.hat, AxisValueToDirection(e.jhat.value));
}
got_event = true;
break;
}
case SDL_JOYDEVICEADDED:
{
// Always remap all controllers.
RemapControllers();
got_event = true;
break;
}
case SDL_JOYDEVICEREMOVED:
{
joystick_states_.erase(e.jdevice.which);
got_event = true;
break;
}
default:
// Ignore all other events.
break;
}
}
wxLongLong now = wxGetUTCTimeMillis();
if (got_event) {
last_poll_ = now;
} else if (now - last_poll_ > kPollTimeInterval) {
for (auto&& joy_state : joystick_states_) {
joy_state.second->Poll();
}
last_poll_ = now;
}
} }
SDL_Joystick*& wxSDLJoyDev::operator=(SDL_Joystick* ptr) void wxSDLJoy::RemapControllers() {
{ if (!is_polling_active_) {
dev_js = ptr; // Nothing to do when we're not actively polling.
return dev_js; return;
}
joystick_states_.clear();
if (requested_sdl_indexes_.empty()) {
// Connect all joysticks.
for (int i = 0; i < SDL_NumJoysticks(); i++) {
std::unique_ptr<wxSDLJoyState> joy_state(new wxSDLJoyState(i));
if (joy_state->IsValid()) {
joystick_states_.emplace(
joy_state->joystick_id(), std::move(joy_state));
}
}
} else {
// Only attempt to add the joysticks we care about.
for (const int& sdl_index : requested_sdl_indexes_) {
std::unique_ptr<wxSDLJoyState> joy_state(
new wxSDLJoyState(sdl_index));
if (joy_state->IsValid()) {
joystick_states_.emplace(
joy_state->joystick_id(), std::move(joy_state));
}
}
}
} }
wxSDLJoyDev::operator bool() wxSDLJoyState* wxSDLJoy::FindJoyState(const SDL_JoystickID& joy_id) {
{ const auto iter = joystick_states_.find(joy_id);
return dev_gc != nullptr; if (iter == joystick_states_.end())
return nullptr;
return iter->second.get();
} }
std::nullptr_t& wxSDLJoyDev::operator=(std::nullptr_t&& null_ptr) void wxSDLJoy::PollJoysticks(std::unordered_set<unsigned> indexes) {
{ // Reset the polling state.
dev_gc = null_ptr; StopPolling();
return null_ptr;
if (indexes.empty()) {
// Nothing to poll. Return early.
return;
}
is_polling_active_ = true;
std::for_each(
indexes.begin(), indexes.end(),
[&](const unsigned& player_index) {
requested_sdl_indexes_.insert(player_index - 1);
});
RemapControllers();
}
void wxSDLJoy::PollAllJoysticks() {
// Reset the polling state.
StopPolling();
is_polling_active_ = true;
RemapControllers();
}
void wxSDLJoy::StopPolling() {
joystick_states_.clear();
requested_sdl_indexes_.clear();
is_polling_active_ = false;
}
void wxSDLJoy::SetRumble(bool activate_rumble) {
if (joystick_states_.empty())
return;
// Do rumble only on the first device.
joystick_states_.begin()->second->SetRumble(activate_rumble);
} }

View File

@ -1,138 +1,103 @@
#ifndef JOYEVT_H #ifndef JOYEVT_H
#define JOYEVT_H #define JOYEVT_H
// This is my own SDL-based joystick handler, since wxJoystick is brain-dead. #include <memory>
// It's geared towards keyboard emulation #include <unordered_set>
// To use, create a wxSDLJoy object Add() the joysticks you want to monitor.
//
// The target window will receive EVT_SDLJOY events of type wxSDLJoyEvent.
#include <cstddef>
#include <array>
#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 <SDL_joystick.h> #include <SDL_joystick.h>
#include <SDL_gamecontroller.h> #include <SDL_gamecontroller.h>
#include "../common/contains.h" #include <SDL_events.h>
struct wxSDLJoyDev { // The different types of supported controls.
private: enum wxSDLControl {
union { WXSDLJOY_AXIS, // Control value is signed 16
SDL_GameController* dev_gc = nullptr; WXSDLJOY_HAT, // Control value is bitmask NESW/URDL
SDL_Joystick* dev_js; WXSDLJOY_BUTTON // Control value is 0 or 1
}; };
// Represents a Joystick event.
class wxSDLJoyEvent : public wxCommandEvent {
public: public:
operator SDL_GameController*&(); wxSDLJoyEvent(
SDL_GameController*& operator=(SDL_GameController* ptr); unsigned player_index,
wxSDLControl control,
uint8_t control_index,
int16_t control_value);
virtual ~wxSDLJoyEvent() = default;
operator SDL_Joystick*&(); unsigned player_index() const { return player_index_; }
SDL_Joystick*& operator=(SDL_Joystick* ptr); wxSDLControl control() const { return control_; }
uint8_t control_index() const { return control_index_; }
int16_t control_value() const { return control_value_; }
operator bool(); private:
unsigned player_index_;
std::nullptr_t& operator=(std::nullptr_t&& null_ptr); wxSDLControl control_;
uint8_t control_index_;
int16_t control_value_;
}; };
struct wxSDLJoyState { class wxSDLJoyState;
wxSDLJoyDev dev;
uint8_t index = 0;
bool is_gc = true;
SDL_JoystickID instance = 0;
std::unordered_map<uint8_t, int16_t> axis{};
std::unordered_map<uint8_t, uint8_t> button{};
};
class wxSDLJoy : public wxTimer { // This is my own SDL-based joystick handler, since wxJoystick is brain-dead.
// It's geared towards keyboard emulation.
//
// After initilization, use PollJoystick() or PollAllJoysticks() for the
// joysticks you wish to monitor. The target window will then receive
// EVT_SDLJOY events of type wxSDLJoyEvent.
// Handling of the player_index() value is different depending on the polling
// mode. After calls to PollJoysticks(), that value will remain constant for a
// given device, even if other joysticks disconnect. This ensures the joystick
// remains active during gameplay even if other joysticks disconnect.
// However, after calls to PollAllJoysticks(), all joysticks are re-connected
// on joystick connect/disconnect. This ensures the right player_index() value
// is sent to the UI during input event configuration.
class wxSDLJoy {
public: public:
wxSDLJoy(); wxSDLJoy();
// add another joystick to the list of polled sticks ~wxSDLJoy();
// -1 == add all
// If joy > # of joysticks, it is ignored
// This will start polling if a valid joystick is selected
void Add(int8_t joy = -1);
// remove a joystick from the polled sticks
// -1 == remove all
// If joy > # of joysticks, it is ignored
// This will stop polling if all joysticks are disabled
void Remove(int8_t joy = -1);
// query if a stick is being polled
bool IsPolling(uint8_t joy) { return contains(joystate, joy); }
// true = currently rumbling, false = turn off rumbling // Adds a set of joysticks to the list of polled joysticks.
void SetRumble(bool do_rumble); // This will disconnect every active joysticks, and reactivates the ones
// matching an index in |indexes|. Missing joysticks will be connected if
// they connect later on.
void PollJoysticks(std::unordered_set<unsigned> indexes);
// Adds all joysticks to the list of polled joysticks. This will
// disconnect every active joysticks, reconnect them and start polling.
void PollAllJoysticks();
// Removes all joysticks from the list of polled joysticks.
// This will stop polling.
void StopPolling();
// Activates or deactivates rumble on active joysticks.
void SetRumble(bool activate_rumble);
// Polls active joysticks and empties the SDL event buffer.
void Poll(); void Poll();
virtual ~wxSDLJoy();
protected:
// used to continue rumbling on a timer
void Notify();
void ConnectController(uint8_t joy);
void RemapControllers();
void DisconnectController(wxSDLJoyState& dev);
void CreateAndSendEvent(unsigned short joy, unsigned short ctrl_type, unsigned short ctrl_idx, short ctrl_val, short prev_val);
const uint8_t POLL_TIME_MS = 25;
private: private:
std::unordered_map<uint8_t, wxSDLJoyState> joystate; // Reconnects all controllers.
std::unordered_map<SDL_JoystickID, wxSDLJoyState*> instance_map; void RemapControllers();
bool add_all = false, rumbling = false;
wxLongLong last_poll = wxGetUTCTimeMillis(); // Helper method to find a joystick state from a joystick ID.
}; // Returns nullptr if not present.
wxSDLJoyState* FindJoyState(const SDL_JoystickID& joy_id);
enum { // Map of SDL joystick ID to joystick state. Only contains active joysticks.
// The types of supported controls std::unordered_map<SDL_JoystickID, std::unique_ptr<wxSDLJoyState>> joystick_states_;
// values are signed-16 for axis, 0/1 for button
// hat is bitmask NESW/URDL
WXSDLJOY_AXIS,
WXSDLJOY_HAT,
WXSDLJOY_BUTTON
};
class wxSDLJoyEvent : public wxCommandEvent { // Set of requested SDL joystick indexes.
friend class wxSDLJoy; std::unordered_set<int> requested_sdl_indexes_;
public: // Set to true when we are actively polling controllers.
// Default constructor bool is_polling_active_ = false;
wxSDLJoyEvent(wxEventType commandType = wxEVT_NULL)
: wxCommandEvent(commandType)
{
}
// accessors
unsigned short GetJoy()
{
return joy;
}
unsigned short GetControlType()
{
return ctrl_type;
}
unsigned short GetControlIndex()
{
return ctrl_idx;
}
short GetControlValue()
{
return ctrl_val;
}
short GetControlPrevValue()
{
return prev_val;
}
protected: // Timestamp when the latest poll was done.
unsigned short joy; wxLongLong last_poll_ = wxGetUTCTimeMillis();
unsigned short ctrl_type;
unsigned short ctrl_idx;
short ctrl_val;
short prev_val;
}; };
// Note: this means sdljoy can't be part of a library w/o extra work // Note: this means sdljoy can't be part of a library w/o extra work

View File

@ -109,8 +109,8 @@ static void get_config_path(wxPathList& path, bool exists = true)
} }
else else
{ {
// config is in $HOME/.vbam/ // config is in $HOME/.vbam/
add_nonstandard_path(old_config); add_nonstandard_path(old_config);
} }
#endif #endif
@ -240,7 +240,7 @@ bool wxvbamApp::OnInit()
return false; return false;
if (console_mode) if (console_mode)
return true; return true;
// prepare for loading xrc files // prepare for loading xrc files
wxXmlResource* xr = wxXmlResource::Get(); wxXmlResource* xr = wxXmlResource::Get();
@ -449,13 +449,13 @@ bool wxvbamApp::OnInit()
return false; return false;
if (x >= 0 && y >= 0 && width > 0 && height > 0) if (x >= 0 && y >= 0 && width > 0 && height > 0)
frame->SetSize(x, y, width, height); frame->SetSize(x, y, width, height);
if (isMaximized) if (isMaximized)
frame->Maximize(); frame->Maximize();
if (isFullscreen && wxGetApp().pending_load != wxEmptyString) if (isFullscreen && wxGetApp().pending_load != wxEmptyString)
frame->ShowFullScreen(isFullscreen); frame->ShowFullScreen(isFullscreen);
frame->Show(true); frame->Show(true);
#ifndef NO_ONLINEUPDATES #ifndef NO_ONLINEUPDATES
@ -468,12 +468,12 @@ int wxvbamApp::OnRun()
{ {
if (console_mode) if (console_mode)
{ {
// we could check for our own error codes here... // we could check for our own error codes here...
return console_status; return console_status;
} }
else else
{ {
return wxApp::OnRun(); return wxApp::OnRun();
} }
} }
@ -512,30 +512,30 @@ void wxvbamApp::OnInitCmdLine(wxCmdLineParser& cl)
static wxCmdLineEntryDesc opttab[] = { static wxCmdLineEntryDesc opttab[] = {
{ wxCMD_LINE_OPTION, NULL, t("save-xrc"), { wxCMD_LINE_OPTION, NULL, t("save-xrc"),
N_("Save built-in XRC file and exit"), N_("Save built-in XRC file and exit"),
wxCMD_LINE_VAL_STRING, 0 }, wxCMD_LINE_VAL_STRING, 0 },
{ wxCMD_LINE_OPTION, NULL, t("save-over"), { wxCMD_LINE_OPTION, NULL, t("save-over"),
N_("Save built-in vba-over.ini and exit"), N_("Save built-in vba-over.ini and exit"),
wxCMD_LINE_VAL_STRING, 0 }, wxCMD_LINE_VAL_STRING, 0 },
{ wxCMD_LINE_SWITCH, NULL, t("print-cfg-path"), { wxCMD_LINE_SWITCH, NULL, t("print-cfg-path"),
N_("Print configuration path and exit"), N_("Print configuration path and exit"),
wxCMD_LINE_VAL_NONE, 0 }, wxCMD_LINE_VAL_NONE, 0 },
{ wxCMD_LINE_SWITCH, t("f"), t("fullscreen"), { wxCMD_LINE_SWITCH, t("f"), t("fullscreen"),
N_("Start in full-screen mode"), N_("Start in full-screen mode"),
wxCMD_LINE_VAL_NONE, 0 }, wxCMD_LINE_VAL_NONE, 0 },
{ wxCMD_LINE_OPTION, t("c"), t("config"), { wxCMD_LINE_OPTION, t("c"), t("config"),
N_("Set a configuration file"), N_("Set a configuration file"),
wxCMD_LINE_VAL_STRING, 0 }, wxCMD_LINE_VAL_STRING, 0 },
#if !defined(NO_LINK) && !defined(__WXMSW__) #if !defined(NO_LINK) && !defined(__WXMSW__)
{ wxCMD_LINE_SWITCH, t("s"), t("delete-shared-state"), { wxCMD_LINE_SWITCH, t("s"), t("delete-shared-state"),
N_("Delete shared link state first, if it exists"), N_("Delete shared link state first, if it exists"),
wxCMD_LINE_VAL_NONE, 0 }, wxCMD_LINE_VAL_NONE, 0 },
#endif #endif
// stupid wx cmd line parser doesn't support duplicate options // stupid wx cmd line parser doesn't support duplicate options
// { wxCMD_LINE_OPTION, t("o"), t("option"), // { wxCMD_LINE_OPTION, t("o"), t("option"),
// _("Set configuration option; <opt>=<value> or help for list"), // _("Set configuration option; <opt>=<value> or help for list"),
{ wxCMD_LINE_SWITCH, t("o"), t("list-options"), { wxCMD_LINE_SWITCH, t("o"), t("list-options"),
N_("List all settable options and exit"), N_("List all settable options and exit"),
wxCMD_LINE_VAL_NONE, 0 }, wxCMD_LINE_VAL_NONE, 0 },
{ wxCMD_LINE_PARAM, NULL, NULL, { wxCMD_LINE_PARAM, NULL, NULL,
N_("ROM file"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, N_("ROM file"), wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_PARAM, NULL, NULL, { wxCMD_LINE_PARAM, NULL, NULL,
@ -720,8 +720,8 @@ wxString wxvbamApp::GetDataDir()
wxvbamApp::~wxvbamApp() { wxvbamApp::~wxvbamApp() {
if (home != NULL) if (home != NULL)
{ {
free(home); free(home);
home = NULL; home = NULL;
} }
delete overrides; delete overrides;
@ -797,8 +797,8 @@ void MainFrame::OnMenu(wxContextMenuEvent& event)
wxPoint p(event.GetPosition()); wxPoint p(event.GetPosition());
#if 0 // wx actually recommends ignoring the position #if 0 // wx actually recommends ignoring the position
if (p != wxDefaultPosition) if (p != wxDefaultPosition)
p = ScreenToClient(p); p = ScreenToClient(p);
#endif #endif
PopupMenu(ctx_menu, p); PopupMenu(ctx_menu, p);
@ -839,16 +839,16 @@ void MainFrame::OnSize(wxSizeEvent& event)
bool isMaximized = IsMaximized(); bool isMaximized = IsMaximized();
if (!isFullscreen && !isMaximized) if (!isFullscreen && !isMaximized)
{ {
if (height > 0 && width > 0) if (height > 0 && width > 0)
{ {
windowHeight = height; windowHeight = height;
windowWidth = width; windowWidth = width;
} }
if (x >= 0 && y >= 0) if (x >= 0 && y >= 0)
{ {
windowPositionX = x; windowPositionX = x;
windowPositionY = y; windowPositionY = y;
} }
} }
else else
{ {
@ -876,25 +876,26 @@ int MainFrame::FilterEvent(wxEvent& event)
evh.SetEventObject(this); evh.SetEventObject(this);
GetEventHandler()->ProcessEvent(evh); GetEventHandler()->ProcessEvent(evh);
return true; return true;
} }
} }
else if (event.GetEventType() == wxEVT_SDLJOY && !menus_opened && !dialog_opened) else if (event.GetEventType() == wxEVT_SDLJOY && !menus_opened && !dialog_opened)
{ {
wxSDLJoyEvent& je = (wxSDLJoyEvent&)event; wxSDLJoyEvent& je = (wxSDLJoyEvent&)event;
if (je.GetControlValue() == 0) return -1; // joystick button UP if (je.control_value() == 0) return -1; // joystick button UP
int key = je.GetControlIndex(); uint8_t key = je.control_index();
int mod = wxJoyKeyTextCtrl::DigitalButton(je); int mod = wxJoyKeyTextCtrl::DigitalButton(je);
int joy = je.GetJoy() + 1; int joy = je.player_index();
wxString label = wxJoyKeyTextCtrl::ToString(mod, key, joy); wxString label = wxJoyKeyTextCtrl::ToString(mod, key, joy);
wxAcceleratorEntry_v accels = wxGetApp().GetAccels(); wxAcceleratorEntry_v accels = wxGetApp().GetAccels();
for (size_t i = 0; i < accels.size(); ++i) { for (size_t i = 0; i < accels.size(); ++i)
if (label == accels[i].GetUkey()) {
{ if (label == accels[i].GetUkey())
wxCommandEvent evh(wxEVT_COMMAND_MENU_SELECTED, accels[i].GetCommand()); {
evh.SetEventObject(this); wxCommandEvent evh(wxEVT_COMMAND_MENU_SELECTED, accels[i].GetCommand());
GetEventHandler()->ProcessEvent(evh); evh.SetEventObject(this);
return true; GetEventHandler()->ProcessEvent(evh);
} return true;
}
} }
} }
return -1; return -1;
@ -916,8 +917,8 @@ wxString MainFrame::GetGamePath(wxString path)
if (!wxIsWritable(game_path)) if (!wxIsWritable(game_path))
{ {
game_path = wxGetApp().GetAbsolutePath(wxString((get_xdg_user_data_home() + DOT_DIR).c_str(), wxConvLibc)); game_path = wxGetApp().GetAbsolutePath(wxString((get_xdg_user_data_home() + DOT_DIR).c_str(), wxConvLibc));
wxFileName::Mkdir(game_path, 0777, wxPATH_MKDIR_FULL); wxFileName::Mkdir(game_path, 0777, wxPATH_MKDIR_FULL);
} }
return game_path; return game_path;
@ -927,23 +928,26 @@ void MainFrame::SetJoystick()
{ {
/* Remove all attached joysticks to avoid errors while /* Remove all attached joysticks to avoid errors while
* destroying and creating the GameArea `panel`. */ * destroying and creating the GameArea `panel`. */
joy.Remove(); joy.StopPolling();
set_global_accels(); set_global_accels();
if (!emulating) if (!emulating)
return; return;
for (int i = 0; i < 4; i++) std::unordered_set<unsigned> needed_joysticks;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < NUM_KEYS; j++) { for (int j = 0; j < NUM_KEYS; j++) {
wxJoyKeyBinding_v b = gopts.joykey_bindings[i][j]; wxJoyKeyBinding_v b = gopts.joykey_bindings[i][j];
for (size_t k = 0; k < b.size(); k++) { for (size_t k = 0; k < b.size(); k++) {
int jn = b[k].joy; int jn = b[k].joy;
if (jn) { if (jn) {
joy.Add(jn - 1); needed_joysticks.insert(jn);
} }
} }
} }
}
joy.PollJoysticks(needed_joysticks);
} }
void MainFrame::StopJoyPollTimer() void MainFrame::StopJoyPollTimer()