diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 765e7cbe..bc6ad734 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -78,8 +78,10 @@ set(VBAM_WX_COMMON widgets/render-plugin.h widgets/user-input-ctrl.cpp widgets/user-input-ctrl.h - widgets/sdljoy.cpp - widgets/sdljoy.h + widgets/user-input-event.cpp + widgets/user-input-event.h + widgets/sdl-poller.cpp + widgets/sdl-poller.h widgets/utils.cpp widgets/utils.h widgets/webupdatedef.h @@ -87,8 +89,6 @@ set(VBAM_WX_COMMON widgets/wxmisc.cpp wxhead.h wxlogdebug.h - wxutil.cpp - wxutil.h wxvbam.cpp wxvbam.h x11keymap.h diff --git a/src/wx/background-input.cpp b/src/wx/background-input.cpp index aa7fd52a..33e50271 100644 --- a/src/wx/background-input.cpp +++ b/src/wx/background-input.cpp @@ -547,17 +547,17 @@ wxThread::ExitCode BackgroundInput::CheckKeyboard() // virtual key "i" is pressed if ((bits & 0x8000) && (previousState[i] & 0x8000) == 0) { if (handler && !wxWindow::FindFocus()) { - wxKeyEvent ev(wxEVT_KEY_DOWN); - ev.m_keyCode = xKeySym; - handler->AddPendingEvent(ev); + wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_DOWN); + event->m_keyCode = xKeySym; + handler->QueueEvent(event); } } // virtual key "i" is released else if (((bits & 0x8000) == 0) && (previousState[i] & 0x8000)) { if (handler && !wxWindow::FindFocus()) { - wxKeyEvent ev(wxEVT_KEY_UP); - ev.m_keyCode = xKeySym; - handler->AddPendingEvent(ev); + wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_UP); + event->m_keyCode = xKeySym; + handler->QueueEvent(event); } } previousState[i] = bits; diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index 62471ca8..f643a426 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -1184,7 +1184,7 @@ EVT_HANDLER(AllowKeyboardBackgroundInput, "Allow keyboard background input (togg disableKeyboardBackgroundInput(); if (OPTION(kUIAllowKeyboardBackgroundInput)) { if (panel && panel->panel) { - enableKeyboardBackgroundInput(panel->panel->GetWindow()); + enableKeyboardBackgroundInput(panel->panel->GetWindow()->GetEventHandler()); } } } @@ -2173,44 +2173,17 @@ EVT_HANDLER(EmulatorDirectories, "Directories...") EVT_HANDLER(JoypadConfigure, "Joypad options...") { - joy.PollAllJoysticks(); - - auto frame = wxGetApp().frame; - bool joy_timer = frame->IsJoyPollTimerRunning(); - - if (!joy_timer) { - frame->StartJoyPollTimer(); - } - if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) { update_joypad_opts(); } - - if (!joy_timer) { - frame->StopJoyPollTimer(); - } - - SetJoystick(); } EVT_HANDLER(Customize, "Customize UI...") { - wxDialog* dlg = GetXRCDialog("AccelConfig"); - joy.PollAllJoysticks(); - - auto frame = wxGetApp().frame; - bool joy_timer = frame->IsJoyPollTimerRunning(); - - if (!joy_timer) frame->StartJoyPollTimer(); - - if (ShowModal(dlg) == wxID_OK) { + if (ShowModal(GetXRCDialog("AccelConfig")) == wxID_OK) { update_shortcut_opts(); ResetMenuAccelerators(); } - - if (!joy_timer) frame->StopJoyPollTimer(); - - SetJoystick(); } #ifndef NO_ONLINEUPDATES diff --git a/src/wx/config/game-control.cpp b/src/wx/config/game-control.cpp index efd1e022..86f6122c 100644 --- a/src/wx/config/game-control.cpp +++ b/src/wx/config/game-control.cpp @@ -2,7 +2,6 @@ #include "wx/opts.h" #include "wx/strutils.h" -#include "wx/wxlogdebug.h" namespace config { diff --git a/src/wx/config/game-control.h b/src/wx/config/game-control.h index 10ef1c0d..5508f924 100644 --- a/src/wx/config/game-control.h +++ b/src/wx/config/game-control.h @@ -1,6 +1,7 @@ #ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_ #define VBAM_WX_CONFIG_GAME_CONTROL_H_ +#include #include #include #include diff --git a/src/wx/config/shortcuts.cpp b/src/wx/config/shortcuts.cpp index ad240015..f83af6d7 100644 --- a/src/wx/config/shortcuts.cpp +++ b/src/wx/config/shortcuts.cpp @@ -87,16 +87,6 @@ int Shortcuts::CommandForInput(const UserInput& input) const { return iter->second; } -std::set Shortcuts::Joysticks() const { - std::set output; - for (const auto& iter : command_to_inputs_) { - for (const UserInput& user_input : iter.second) { - output.insert(user_input.joystick()); - } - } - return output; -} - Shortcuts Shortcuts::Clone() const { return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_); } diff --git a/src/wx/config/shortcuts.h b/src/wx/config/shortcuts.h index 8a54af10..5ce61c14 100644 --- a/src/wx/config/shortcuts.h +++ b/src/wx/config/shortcuts.h @@ -9,7 +9,7 @@ #include "wx/config/user-input.h" -// by default, only 9 recent items +// wxWidgets only goes up to `wxID_FILE9` but we want 10 recent files. #define wxID_FILE10 (wxID_FILE9 + 1) namespace config { @@ -45,9 +45,6 @@ public: // Returns the command currently assigned to `input` or nullptr if none. int CommandForInput(const UserInput& input) const; - // Returns the set of joysticks used by shortcuts. - std::set Joysticks() const; - // Returns a copy of this object. This can be an expensive operation and // should only be used to modify the currently active shortcuts // configuration. diff --git a/src/wx/config/user-input.cpp b/src/wx/config/user-input.cpp index b7480c3f..84c3c023 100644 --- a/src/wx/config/user-input.cpp +++ b/src/wx/config/user-input.cpp @@ -8,7 +8,6 @@ #include #include "wx/strutils.h" -#include "wx/wxutil.h" namespace config { @@ -202,26 +201,26 @@ UserInput StringToUserInput(const wxString& string) { if (kAxisRegex.Matches(remainder)) { kAxisRegex.GetMatch(&start, &length, 1); const int key = StringToInt(remainder.Mid(start, length)); - const int mod = - remainder[start + length] == '+' ? wxJoyControl::AxisPlus : wxJoyControl::AxisMinus; - return UserInput(key, mod, joy); + const JoyControl control = + remainder[start + length] == '+' ? JoyControl::AxisPlus : JoyControl::AxisMinus; + return UserInput(key, control, JoyId(joy - 1)); } if (kButtonRegex.Matches(remainder)) { kButtonRegex.GetMatch(&start, &length, 1); const int key = StringToInt(remainder.Mid(start, length)); - return UserInput(key, wxJoyControl::Button, joy); + return UserInput(key, JoyControl::Button, JoyId(joy - 1)); } if (kHatRegex.Matches(remainder)) { kHatRegex.GetMatch(&start, &length, 1); const int key = StringToInt(remainder.Mid(start, length)); if (kHatRegex.GetMatch(remainder, 3).Length()) { - return UserInput(key, wxJoyControl::HatNorth, joy); + return UserInput(key, JoyControl::HatNorth, JoyId(joy - 1)); } else if (kHatRegex.GetMatch(remainder, 4).Length()) { - return UserInput(key, wxJoyControl::HatSouth, joy); + return UserInput(key, JoyControl::HatSouth, JoyId(joy - 1)); } else if (kHatRegex.GetMatch(remainder, 5).Length()) { - return UserInput(key, wxJoyControl::HatEast, joy); + return UserInput(key, JoyControl::HatEast, JoyId(joy - 1)); } else if (kHatRegex.GetMatch(remainder, 6).Length()) { - return UserInput(key, wxJoyControl::HatWest, joy); + return UserInput(key, JoyControl::HatWest, JoyId(joy - 1)); } } @@ -267,14 +266,47 @@ UserInput StringToUserInput(const wxString& string) { } // namespace -UserInput::UserInput(const wxKeyEvent& event) - : UserInput(Device::Keyboard, event.GetModifiers(), getKeyboardKeyCode(event), 0) {} +// static +JoyId JoyId::Invalid() { + static constexpr int kInvalidSdlIndex = -1; + return JoyId(kInvalidSdlIndex); +} -UserInput::UserInput(const wxJoyEvent& event) +wxString JoyId::ToString() { + return wxString::Format("Joy%d", sdl_index_ + 1); +} + +bool JoyId::operator==(const JoyId& other) const { + return sdl_index_ == other.sdl_index_; +} +bool JoyId::operator!=(const JoyId& other) const { + return !(*this == other); +} +bool JoyId::operator<(const JoyId& other) const { + return sdl_index_ < other.sdl_index_; +} +bool JoyId::operator<=(const JoyId& other) const { + return !(*this > other); +} +bool JoyId::operator>(const JoyId& other) const { + return other < *this; +} +bool JoyId::operator>=(const JoyId& other) const { + return !(*this < other); +} + +JoyId::JoyId(int sdl_index) : sdl_index_(sdl_index) {} + +UserInput::UserInput(uint8_t control_index, JoyControl control, JoyId joystick) : UserInput(Device::Joystick, - event.control(), - event.control_index(), - event.joystick().player_index()) {} + static_cast(control), + control_index, + joystick.sdl_index_ + 1) {} + +UserInput::UserInput(wxKeyCode key, wxKeyModifier mod) + : UserInput(Device::Keyboard, mod, key, 0) {} + +UserInput::UserInput(char c, wxKeyModifier mod) : UserInput(Device::Keyboard, mod, c, 0) {} // static std::set UserInput::FromConfigString(const wxString& string) { @@ -315,26 +347,26 @@ wxString UserInput::ToConfigString() const { return KeyboardInputToConfigString(mod_, key_); case Device::Joystick: wxString key; - switch (mod_) { - case wxJoyControl::AxisPlus: + switch (static_cast(mod_)) { + case JoyControl::AxisPlus: key = wxString::Format(("Axis%d+"), key_); break; - case wxJoyControl::AxisMinus: + case JoyControl::AxisMinus: key = wxString::Format(("Axis%d-"), key_); break; - case wxJoyControl::Button: + case JoyControl::Button: key = wxString::Format(("Button%d"), key_); break; - case wxJoyControl::HatNorth: + case JoyControl::HatNorth: key = wxString::Format(("Hat%dN"), key_); break; - case wxJoyControl::HatSouth: + case JoyControl::HatSouth: key = wxString::Format(("Hat%dS"), key_); break; - case wxJoyControl::HatWest: + case JoyControl::HatWest: key = wxString::Format(("Hat%dW"), key_); break; - case wxJoyControl::HatEast: + case JoyControl::HatEast: key = wxString::Format(("Hat%dE"), key_); break; } diff --git a/src/wx/config/user-input.h b/src/wx/config/user-input.h index 2ce83d47..284d1566 100644 --- a/src/wx/config/user-input.h +++ b/src/wx/config/user-input.h @@ -1,14 +1,56 @@ #ifndef VBAM_WX_CONFIG_USER_INPUT_H_ #define VBAM_WX_CONFIG_USER_INPUT_H_ -#include -#include +#include #include -#include "wx/widgets/sdljoy.h" +#include +#include +#include namespace config { +// Forward declaration. +class UserInput; + +// One of the possible joystick controls. +enum class JoyControl { + AxisPlus = 0, + AxisMinus, + Button, + HatNorth, + HatSouth, + HatWest, + HatEast, + Last = HatEast +}; + +// Abstraction for a single joystick. In the current implementation, this +// encapsulates an `sdl_index_`. +class JoyId { +public: + static JoyId Invalid(); + + explicit JoyId(int sdl_index); + virtual ~JoyId() = default; + + wxString ToString(); + + bool operator==(const JoyId& other) const; + bool operator!=(const JoyId& other) const; + bool operator<(const JoyId& other) const; + bool operator<=(const JoyId& other) const; + bool operator>(const JoyId& other) const; + bool operator>=(const JoyId& other) const; + +private: + JoyId() = delete; + + int sdl_index_; + + friend class UserInput; +}; + // Abstraction for a user input, which can come from a keyboard or a joystick. // This class implements comparison operators so it can be used in sets and as // a key in maps. @@ -34,14 +76,15 @@ public: // Invalid UserInput, mainly used for comparison. UserInput() : UserInput(Device::Invalid, 0, 0, 0) {} - // Constructor from a wxKeyEvent. - UserInput(const wxKeyEvent& event); + // Constructor for a joystick input. + UserInput(uint8_t control_index, JoyControl control, JoyId joystick); - // Constructor from a wxJoyEvent. - UserInput(const wxJoyEvent& event); + // Constructors for a keyboard input. + UserInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE); + UserInput(char c, wxKeyModifier mod = wxMOD_NONE); // TODO: Remove this once all uses have been removed. - UserInput(int key, int mod = 0, int joy = 0) + explicit UserInput(int key, int mod = 0, int joy = 0) : UserInput(joy == 0 ? Device::Keyboard : Device::Joystick, mod, key, @@ -55,14 +98,27 @@ public: // Converts to a localized string for display. wxString ToLocalizedString() const; - wxJoystick joystick() const { return joystick_; } + JoyId joystick() const { return joystick_; } constexpr bool is_valid() const { return device_ != Device::Invalid; } constexpr operator bool() const { return is_valid(); } + bool is_joystick() const { return device_ == Device::Joystick; } + bool is_keyboard() const { return device_ == Device::Keyboard; } + int key() const { return key_; } int mod() const { return mod_; } unsigned joy() const { return joy_; } + JoyControl joy_control() const { + assert(is_joystick()); + return static_cast(mod_); + } + + wxKeyCode key_code() const { + assert(is_keyboard()); + return static_cast(key_); + } + constexpr bool operator==(const UserInput& other) const { return device_ == other.device_ && mod_ == other.mod_ && key_ == other.key_ && joy_ == other.joy_; @@ -98,14 +154,14 @@ public: private: UserInput(Device device, int mod, int key, unsigned joy) : device_(device), - joystick_(joy == 0 ? wxJoystick::Invalid() - : wxJoystick::FromLegacyPlayerIndex(joy)), + joystick_(joy == 0 ? JoyId::Invalid() + : JoyId(joy - 1)), mod_(mod), key_(key), joy_(joy) {} Device device_; - wxJoystick joystick_; + JoyId joystick_; int mod_; int key_; unsigned joy_; diff --git a/src/wx/dialogs/joypad-config.cpp b/src/wx/dialogs/joypad-config.cpp index 43c61311..b10cce78 100644 --- a/src/wx/dialogs/joypad-config.cpp +++ b/src/wx/dialogs/joypad-config.cpp @@ -1,13 +1,13 @@ #include "wx/dialogs/joypad-config.h" -#include +#include #include "wx/config/option-proxy.h" #include "wx/dialogs/base-dialog.h" +#include "wx/opts.h" #include "wx/widgets/option-validator.h" #include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/utils.h" -#include "wx/wxvbam.h" namespace dialogs { @@ -87,7 +87,6 @@ void JoypadConfig::ToggleSDLGameControllerMode() { OPTION(kSDLGameControllerMode) = GetValidatedChild("SDLGameControllerMode")->IsChecked(); ClearAllJoypads(); - wxGetApp().frame->PollAllJoysticks(); } void JoypadConfig::ClearAllJoypads() { diff --git a/src/wx/dialogs/joypad-config.h b/src/wx/dialogs/joypad-config.h index 92af73f6..6c8fbd5b 100644 --- a/src/wx/dialogs/joypad-config.h +++ b/src/wx/dialogs/joypad-config.h @@ -2,6 +2,7 @@ #define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ #include "wx/dialogs/base-dialog.h" + namespace dialogs { // Manages the Joypad configuration dialog. @@ -24,7 +25,7 @@ private: // Clears all Joypad controls for all Joypads. void ClearAllJoypads(); - + // Toggle SDL GameController mode for all joysticks. void ToggleSDLGameControllerMode(); }; diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp index cdc4d411..bca1beb9 100644 --- a/src/wx/opts.cpp +++ b/src/wx/opts.cpp @@ -105,142 +105,151 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg, } // namespace -#define WJKB config::UserInput - opts_t gopts; const std::map> kDefaultBindings = { - { config::GameControl(0, config::GameKey::Up), { - WJKB('W'), - WJKB(11, wxJoyControl::Button, 1), - WJKB(1, wxJoyControl::AxisMinus, 1), - WJKB(3, wxJoyControl::AxisMinus, 1), - }}, - { config::GameControl(0, config::GameKey::Down), { - WJKB('S'), - WJKB(12, wxJoyControl::Button, 1), - WJKB(1, wxJoyControl::AxisPlus, 1), - WJKB(3, wxJoyControl::AxisPlus, 1), - }}, - { config::GameControl(0, config::GameKey::Left), { - WJKB('A'), - WJKB(13, wxJoyControl::Button, 1), - WJKB(0, wxJoyControl::AxisMinus, 1), - WJKB(2, wxJoyControl::AxisMinus, 1), - }}, - { config::GameControl(0, config::GameKey::Right), { - WJKB('D'), - WJKB(14, wxJoyControl::Button, 1), - WJKB(0, wxJoyControl::AxisPlus, 1), - WJKB(2, wxJoyControl::AxisPlus, 1), - }}, - { config::GameControl(0, config::GameKey::A), { - WJKB('L'), - WJKB(0, wxJoyControl::Button, 1), - }}, - { config::GameControl(0, config::GameKey::B), { - WJKB('K'), - WJKB(1, wxJoyControl::Button, 1), - }}, - { config::GameControl(0, config::GameKey::L), { - WJKB('I'), - WJKB(2, wxJoyControl::Button, 1), - WJKB(9, wxJoyControl::Button, 1), - WJKB(4, wxJoyControl::AxisPlus, 1), - }}, - { config::GameControl(0, config::GameKey::R), { - WJKB('O'), - WJKB(3, wxJoyControl::Button, 1), - WJKB(10, wxJoyControl::Button, 1), - WJKB(5, wxJoyControl::AxisPlus, 1), - }}, - { config::GameControl(0, config::GameKey::Select), { - WJKB(WXK_BACK), - WJKB(4, wxJoyControl::Button, 1), - }}, - { config::GameControl(0, config::GameKey::Start), { - WJKB(WXK_RETURN), - WJKB(6, wxJoyControl::Button, 1), - }}, - { config::GameControl(0, config::GameKey::MotionUp), {}}, - { config::GameControl(0, config::GameKey::MotionDown), {}}, - { config::GameControl(0, config::GameKey::MotionLeft), {}}, - { config::GameControl(0, config::GameKey::MotionRight), {}}, - { config::GameControl(0, config::GameKey::MotionIn), {}}, - { config::GameControl(0, config::GameKey::MotionOut), {}}, - { config::GameControl(0, config::GameKey::AutoA), {}}, - { config::GameControl(0, config::GameKey::AutoB), {}}, - { config::GameControl(0, config::GameKey::Speed), { - WJKB(WXK_SPACE), - }}, - { config::GameControl(0, config::GameKey::Capture), {}}, - { config::GameControl(0, config::GameKey::Gameshark), {}}, + {config::GameControl(0, config::GameKey::Up), + { + config::UserInput('W'), + config::UserInput(11, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(1, config::JoyControl::AxisMinus, config::JoyId(0)), + config::UserInput(3, config::JoyControl::AxisMinus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::Down), + { + config::UserInput('S'), + config::UserInput(12, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(1, config::JoyControl::AxisPlus, config::JoyId(0)), + config::UserInput(3, config::JoyControl::AxisPlus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::Left), + { + config::UserInput('A'), + config::UserInput(13, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(0, config::JoyControl::AxisMinus, config::JoyId(0)), + config::UserInput(2, config::JoyControl::AxisMinus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::Right), + { + config::UserInput('D'), + config::UserInput(14, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(0, config::JoyControl::AxisPlus, config::JoyId(0)), + config::UserInput(2, config::JoyControl::AxisPlus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::A), + { + config::UserInput('L'), + config::UserInput(0, config::JoyControl::Button, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::B), + { + config::UserInput('K'), + config::UserInput(1, config::JoyControl::Button, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::L), + { + config::UserInput('I'), + config::UserInput(2, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(9, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(4, config::JoyControl::AxisPlus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::R), + { + config::UserInput('O'), + config::UserInput(3, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(10, config::JoyControl::Button, config::JoyId(0)), + config::UserInput(5, config::JoyControl::AxisPlus, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::Select), + { + config::UserInput(WXK_BACK), + config::UserInput(4, config::JoyControl::Button, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::Start), + { + config::UserInput(WXK_RETURN), + config::UserInput(6, config::JoyControl::Button, config::JoyId(0)), + }}, + {config::GameControl(0, config::GameKey::MotionUp), {}}, + {config::GameControl(0, config::GameKey::MotionDown), {}}, + {config::GameControl(0, config::GameKey::MotionLeft), {}}, + {config::GameControl(0, config::GameKey::MotionRight), {}}, + {config::GameControl(0, config::GameKey::MotionIn), {}}, + {config::GameControl(0, config::GameKey::MotionOut), {}}, + {config::GameControl(0, config::GameKey::AutoA), {}}, + {config::GameControl(0, config::GameKey::AutoB), {}}, + {config::GameControl(0, config::GameKey::Speed), + { + config::UserInput(WXK_SPACE), + }}, + {config::GameControl(0, config::GameKey::Capture), {}}, + {config::GameControl(0, config::GameKey::Gameshark), {}}, - { config::GameControl(1, config::GameKey::Up), {}}, - { config::GameControl(1, config::GameKey::Down), {}}, - { config::GameControl(1, config::GameKey::Left), {}}, - { config::GameControl(1, config::GameKey::Right), {}}, - { config::GameControl(1, config::GameKey::A), {}}, - { config::GameControl(1, config::GameKey::B), {}}, - { config::GameControl(1, config::GameKey::L), {}}, - { config::GameControl(1, config::GameKey::R), {}}, - { config::GameControl(1, config::GameKey::Select), {}}, - { config::GameControl(1, config::GameKey::Start), {}}, - { config::GameControl(1, config::GameKey::MotionUp), {}}, - { config::GameControl(1, config::GameKey::MotionDown), {}}, - { config::GameControl(1, config::GameKey::MotionLeft), {}}, - { config::GameControl(1, config::GameKey::MotionRight), {}}, - { config::GameControl(1, config::GameKey::MotionIn), {}}, - { config::GameControl(1, config::GameKey::MotionOut), {}}, - { config::GameControl(1, config::GameKey::AutoA), {}}, - { config::GameControl(1, config::GameKey::AutoB), {}}, - { config::GameControl(1, config::GameKey::Speed), {}}, - { config::GameControl(1, config::GameKey::Capture), {}}, - { config::GameControl(1, config::GameKey::Gameshark), {}}, + {config::GameControl(1, config::GameKey::Up), {}}, + {config::GameControl(1, config::GameKey::Down), {}}, + {config::GameControl(1, config::GameKey::Left), {}}, + {config::GameControl(1, config::GameKey::Right), {}}, + {config::GameControl(1, config::GameKey::A), {}}, + {config::GameControl(1, config::GameKey::B), {}}, + {config::GameControl(1, config::GameKey::L), {}}, + {config::GameControl(1, config::GameKey::R), {}}, + {config::GameControl(1, config::GameKey::Select), {}}, + {config::GameControl(1, config::GameKey::Start), {}}, + {config::GameControl(1, config::GameKey::MotionUp), {}}, + {config::GameControl(1, config::GameKey::MotionDown), {}}, + {config::GameControl(1, config::GameKey::MotionLeft), {}}, + {config::GameControl(1, config::GameKey::MotionRight), {}}, + {config::GameControl(1, config::GameKey::MotionIn), {}}, + {config::GameControl(1, config::GameKey::MotionOut), {}}, + {config::GameControl(1, config::GameKey::AutoA), {}}, + {config::GameControl(1, config::GameKey::AutoB), {}}, + {config::GameControl(1, config::GameKey::Speed), {}}, + {config::GameControl(1, config::GameKey::Capture), {}}, + {config::GameControl(1, config::GameKey::Gameshark), {}}, - { config::GameControl(2, config::GameKey::Up), {}}, - { config::GameControl(2, config::GameKey::Down), {}}, - { config::GameControl(2, config::GameKey::Left), {}}, - { config::GameControl(2, config::GameKey::Right), {}}, - { config::GameControl(2, config::GameKey::A), {}}, - { config::GameControl(2, config::GameKey::B), {}}, - { config::GameControl(2, config::GameKey::L), {}}, - { config::GameControl(2, config::GameKey::R), {}}, - { config::GameControl(2, config::GameKey::Select), {}}, - { config::GameControl(2, config::GameKey::Start), {}}, - { config::GameControl(2, config::GameKey::MotionUp), {}}, - { config::GameControl(2, config::GameKey::MotionDown), {}}, - { config::GameControl(2, config::GameKey::MotionLeft), {}}, - { config::GameControl(2, config::GameKey::MotionRight), {}}, - { config::GameControl(2, config::GameKey::MotionIn), {}}, - { config::GameControl(2, config::GameKey::MotionOut), {}}, - { config::GameControl(2, config::GameKey::AutoA), {}}, - { config::GameControl(2, config::GameKey::AutoB), {}}, - { config::GameControl(2, config::GameKey::Speed), {}}, - { config::GameControl(2, config::GameKey::Capture), {}}, - { config::GameControl(2, config::GameKey::Gameshark), {}}, + {config::GameControl(2, config::GameKey::Up), {}}, + {config::GameControl(2, config::GameKey::Down), {}}, + {config::GameControl(2, config::GameKey::Left), {}}, + {config::GameControl(2, config::GameKey::Right), {}}, + {config::GameControl(2, config::GameKey::A), {}}, + {config::GameControl(2, config::GameKey::B), {}}, + {config::GameControl(2, config::GameKey::L), {}}, + {config::GameControl(2, config::GameKey::R), {}}, + {config::GameControl(2, config::GameKey::Select), {}}, + {config::GameControl(2, config::GameKey::Start), {}}, + {config::GameControl(2, config::GameKey::MotionUp), {}}, + {config::GameControl(2, config::GameKey::MotionDown), {}}, + {config::GameControl(2, config::GameKey::MotionLeft), {}}, + {config::GameControl(2, config::GameKey::MotionRight), {}}, + {config::GameControl(2, config::GameKey::MotionIn), {}}, + {config::GameControl(2, config::GameKey::MotionOut), {}}, + {config::GameControl(2, config::GameKey::AutoA), {}}, + {config::GameControl(2, config::GameKey::AutoB), {}}, + {config::GameControl(2, config::GameKey::Speed), {}}, + {config::GameControl(2, config::GameKey::Capture), {}}, + {config::GameControl(2, config::GameKey::Gameshark), {}}, - { config::GameControl(3, config::GameKey::Up), {}}, - { config::GameControl(3, config::GameKey::Down), {}}, - { config::GameControl(3, config::GameKey::Left), {}}, - { config::GameControl(3, config::GameKey::Right), {}}, - { config::GameControl(3, config::GameKey::A), {}}, - { config::GameControl(3, config::GameKey::B), {}}, - { config::GameControl(3, config::GameKey::L), {}}, - { config::GameControl(3, config::GameKey::R), {}}, - { config::GameControl(3, config::GameKey::Select), {}}, - { config::GameControl(3, config::GameKey::Start), {}}, - { config::GameControl(3, config::GameKey::MotionUp), {}}, - { config::GameControl(3, config::GameKey::MotionDown), {}}, - { config::GameControl(3, config::GameKey::MotionLeft), {}}, - { config::GameControl(3, config::GameKey::MotionRight), {}}, - { config::GameControl(3, config::GameKey::MotionIn), {}}, - { config::GameControl(3, config::GameKey::MotionOut), {}}, - { config::GameControl(3, config::GameKey::AutoA), {}}, - { config::GameControl(3, config::GameKey::AutoB), {}}, - { config::GameControl(3, config::GameKey::Speed), {}}, - { config::GameControl(3, config::GameKey::Capture), {}}, - { config::GameControl(3, config::GameKey::Gameshark), {}}, + {config::GameControl(3, config::GameKey::Up), {}}, + {config::GameControl(3, config::GameKey::Down), {}}, + {config::GameControl(3, config::GameKey::Left), {}}, + {config::GameControl(3, config::GameKey::Right), {}}, + {config::GameControl(3, config::GameKey::A), {}}, + {config::GameControl(3, config::GameKey::B), {}}, + {config::GameControl(3, config::GameKey::L), {}}, + {config::GameControl(3, config::GameKey::R), {}}, + {config::GameControl(3, config::GameKey::Select), {}}, + {config::GameControl(3, config::GameKey::Start), {}}, + {config::GameControl(3, config::GameKey::MotionUp), {}}, + {config::GameControl(3, config::GameKey::MotionDown), {}}, + {config::GameControl(3, config::GameKey::MotionLeft), {}}, + {config::GameControl(3, config::GameKey::MotionRight), {}}, + {config::GameControl(3, config::GameKey::MotionIn), {}}, + {config::GameControl(3, config::GameKey::MotionOut), {}}, + {config::GameControl(3, config::GameKey::AutoA), {}}, + {config::GameControl(3, config::GameKey::AutoB), {}}, + {config::GameControl(3, config::GameKey::Speed), {}}, + {config::GameControl(3, config::GameKey::Capture), {}}, + {config::GameControl(3, config::GameKey::Gameshark), {}}, }; // This constructor only works with globally allocated gopts. diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index e827c3e3..13c54763 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -1,10 +1,8 @@ +#include "wx/wxvbam.h" + #include #include #include -#include "components/filters_agb/filters_agb.h" -#include "components/filters_interframe/interframe.h" -#include "core/base/system.h" -#include "wx/config/option-id.h" #ifdef __WXGTK__ #include @@ -22,13 +20,14 @@ #include #include -#include -#include "background-input.h" #include "components/draw_text/draw_text.h" #include "components/filters/filters.h" +#include "components/filters_agb/filters_agb.h" +#include "components/filters_interframe/interframe.h" #include "core/base/file_util.h" #include "core/base/patch.h" +#include "core/base/system.h" #include "core/base/version.h" #include "core/gb/gb.h" #include "core/gb/gbCheats.h" @@ -41,15 +40,15 @@ #include "core/gba/gbaPrint.h" #include "core/gba/gbaRtc.h" #include "core/gba/gbaSound.h" +#include "wx/background-input.h" #include "wx/config/game-control.h" +#include "wx/config/option-id.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" -#include "wx/config/user-input.h" #include "wx/drawing.h" #include "wx/wayland.h" #include "wx/widgets/render-plugin.h" -#include "wx/wxutil.h" -#include "wx/wxvbam.h" +#include "wx/widgets/user-input-event.h" #ifdef __WXMSW__ #include @@ -165,6 +164,7 @@ GameArea::GameArea() config::OptionID::kSoundBuffers, config::OptionID::kSoundDSoundHWAccel, config::OptionID::kSoundUpmix}, [&](config::Option*) { schedule_audio_restart_ = true; }) { + this->SetClientObject(new widgets::UserInputEventSender(this)); SetSizer(new wxBoxSizer(wxVERTICAL)); // all renderers prefer 32-bit // well, "simple" prefers 24-bit, but that's not available for filters @@ -440,8 +440,6 @@ void GameArea::LoadGame(const wxString& name) was_paused = true; schedule_audio_restart_ = false; MainFrame* mf = wxGetApp().frame; - mf->StopJoyPollTimer(); - mf->SetJoystick(); mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA); mf->cmd_enable |= ONLOAD_CMDEN; mf->cmd_enable |= loaded == IMAGE_GB ? CMDEN_GB : (CMDEN_GBA | CMDEN_NGDB_GBA); @@ -697,8 +695,6 @@ void GameArea::UnloadGame(bool destruct) mf->cmd_enable &= UNLOAD_CMDEN_KEEP; mf->update_state_ts(true); mf->enable_menus(); - mf->StartJoyPollTimer(); - mf->SetJoystick(); mf->ResetCheatSearch(); if (rewind_mem) @@ -1087,8 +1083,6 @@ void GameArea::Pause() if (loaded != IMAGE_UNKNOWN) soundPause(); - - wxGetApp().frame->StartJoyPollTimer(); } void GameArea::Resume() @@ -1103,8 +1097,6 @@ void GameArea::Resume() if (loaded != IMAGE_UNKNOWN) soundResume(); - wxGetApp().frame->StopJoyPollTimer(); - SetFocus(); } @@ -1184,31 +1176,32 @@ void GameArea::OnIdle(wxIdleEvent& event) wxWindow* w = panel->GetWindow(); // set up event handlers - w->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this); - w->Connect(wxEVT_KEY_UP, wxKeyEventHandler(GameArea::OnKeyUp), NULL, this); - w->Connect(wxEVT_PAINT, wxPaintEventHandler(GameArea::PaintEv), NULL, this); - w->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(GameArea::EraseBackground), NULL, this); + w->Bind(VBAM_EVT_USER_INPUT_DOWN, &GameArea::OnUserInputDown, this); + w->Bind(VBAM_EVT_USER_INPUT_UP, &GameArea::OnUserInputUp, this); + w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this); + w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this); // set userdata so we know it's the panel and not the frame being resized // the userdata is freed on disconnect/destruction - this->Connect(wxEVT_SIZE, wxSizeEventHandler(GameArea::OnSize), NULL, this); + this->Bind(wxEVT_SIZE, &GameArea::OnSize, this); // We need to check if the buttons stayed pressed when focus the panel. - w->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(GameArea::OnKillFocus), NULL, this); + w->Bind(wxEVT_KILL_FOCUS, &GameArea::OnKillFocus, this); // Update mouse last-used timers on mouse events etc.. - w->Connect(wxEVT_MOTION, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); - w->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); - w->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); - w->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); - w->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); + w->Bind(wxEVT_MOTION, &GameArea::MouseEvent, this); + w->Bind(wxEVT_LEFT_DOWN, &GameArea::MouseEvent, this); + w->Bind(wxEVT_RIGHT_DOWN, &GameArea::MouseEvent, this); + w->Bind(wxEVT_MIDDLE_DOWN, &GameArea::MouseEvent, this); + w->Bind(wxEVT_MOUSEWHEEL, &GameArea::MouseEvent, this); w->SetBackgroundStyle(wxBG_STYLE_CUSTOM); w->SetSize(wxSize(basic_width, basic_height)); // Allow input while on background - if (OPTION(kUIAllowKeyboardBackgroundInput)) - enableKeyboardBackgroundInput(w); + if (OPTION(kUIAllowKeyboardBackgroundInput)) { + enableKeyboardBackgroundInput(w->GetEventHandler()); + } if (gopts.max_scale) w->SetMaxSize(wxSize(basic_width * gopts.max_scale, @@ -1251,8 +1244,6 @@ void GameArea::OnIdle(wxIdleEvent& event) UpdateLcdFilter(); } - mf->PollJoysticks(); - if (!paused) { HidePointer(); HideMenuBar(); @@ -1323,14 +1314,6 @@ void GameArea::OnIdle(wxIdleEvent& event) } } -static bool process_user_input(bool down, const config::UserInput& user_input) -{ - if (down) - return config::GameControlState::Instance().OnInputPressed(user_input); - else - return config::GameControlState::Instance().OnInputReleased(user_input); -} - static void draw_black_background(wxWindow* win) { wxClientDC dc(win); wxCoord w, h; @@ -1340,70 +1323,32 @@ static void draw_black_background(wxWindow* win) { dc.DrawRectangle(0, 0, w, h); } -static void process_keyboard_event(const wxKeyEvent& ev, bool down) -{ - int kc = ev.GetKeyCode(); - - // Under Wayland or if the key is unicode, we can't use wxGetKeyState(). - if (!IsWayland() && kc != WXK_NONE) { - // Check if the key state corresponds to the event. - if (down != wxGetKeyState(static_cast(kc))) { - return; - } - } - - int key = getKeyboardKeyCode(ev); - int mod = ev.GetModifiers(); - - if (key == WXK_NONE) { - return; - } - - // modifier-only key releases do not set the modifier flag - // so we set it here to match key release events to key press events - switch (key) { - case WXK_SHIFT: - mod |= wxMOD_SHIFT; - break; - case WXK_ALT: - mod |= wxMOD_ALT; - break; - case WXK_CONTROL: - mod |= wxMOD_CONTROL; - break; -#ifdef __WXMAC__ - case WXK_RAW_CONTROL: - mod |= wxMOD_RAW_CONTROL; - break; -#endif - default: - // This resets mod for any non-modifier key. This has the side - // effect of not being able to set up a modifier+key for a game - // control. - mod = 0; - break; - } - - if (process_user_input(down, config::UserInput(key, mod, 0))) { - wxWakeUpIdle(); - }; -} - #ifdef __WXGTK__ -static Display* GetX11Display() -{ +static Display* GetX11Display() { return GDK_WINDOW_XDISPLAY(gtk_widget_get_window(wxGetApp().frame->GetHandle())); } -#endif // __WXGTK__ +#endif // __WXGTK__ -void GameArea::OnKeyDown(wxKeyEvent& ev) -{ - process_keyboard_event(ev, true); +void GameArea::OnUserInputDown(widgets::UserInputEvent& event) { + if (config::GameControlState::Instance().OnInputPressed(event.input())) { + wxWakeUpIdle(); + } } -void GameArea::OnKeyUp(wxKeyEvent& ev) -{ - process_keyboard_event(ev, false); +void GameArea::OnUserInputUp(widgets::UserInputEvent& event) { + if (config::GameControlState::Instance().OnInputReleased(event.input())) { + wxWakeUpIdle(); + } + + // tell Linux to turn off the screensaver/screen-blank if joystick button was pressed + // this shouldn't be necessary of course +#if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS) + if (event.input().is_joystick() && !wxGetApp().UsingWayland()) { + auto display = GetX11Display(); + XResetScreenSaver(display); + XFlush(display); + } +#endif } // these three are forwarded to the DrawingPanel instance @@ -1430,24 +1375,8 @@ void GameArea::OnSize(wxSizeEvent& ev) ev.Skip(); } -void GameArea::OnSDLJoy(wxJoyEvent& ev) -{ - process_user_input(ev.pressed(), config::UserInput(ev)); - - // tell Linux to turn off the screensaver/screen-blank if joystick button was pressed - // this shouldn't be necessary of course -#if defined(__WXGTK__) && defined(HAVE_X11) && !defined(HAVE_XSS) - if (!wxGetApp().UsingWayland()) { - auto display = GetX11Display(); - XResetScreenSaver(display); - XFlush(display); - } -#endif -} - BEGIN_EVENT_TABLE(GameArea, wxPanel) EVT_IDLE(GameArea::OnIdle) -EVT_SDLJOY(GameArea::OnSDLJoy) // FIXME: wxGTK does not generate motion events in MainFrame (not sure // what to do about it) EVT_MOUSE_EVENTS(GameArea::MouseEvent) @@ -1525,6 +1454,7 @@ DrawingPanel::DrawingPanel(wxWindow* parent, int _width, int _height) , wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { + this->SetClientData(new widgets::UserInputEventSender(this)); } void DrawingPanelBase::DrawingPanelInit() @@ -2256,6 +2186,7 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height) , wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { + this->SetClientData(new widgets::UserInputEventSender(this)); widgets::RequestHighResolutionOpenGlSurfaceForWindow(this); SetContext(); } diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index 95ceccc4..b435bc32 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -596,7 +596,7 @@ uint32_t systemGetClock() void systemCartridgeRumble(bool b) { - wxGetApp().frame->SetJoystickRumble(b); + wxGetApp().sdl_poller()->SetRumble(b); } static uint8_t sensorDarkness = 0xE8; // total darkness (including daylight on rainy days) diff --git a/src/wx/viewsupt.cpp b/src/wx/viewsupt.cpp index 2daa82b2..b6b608d5 100644 --- a/src/wx/viewsupt.cpp +++ b/src/wx/viewsupt.cpp @@ -1,10 +1,38 @@ #include "wx/viewsupt.h" #include "wx/config/option-proxy.h" -#include "wx/wxutil.h" #include "wx/wxvbam.h" namespace Viewers { + +namespace { + +int getKeyboardKeyCode(const wxKeyEvent& event) { + const int key_code = event.GetKeyCode(); + if (key_code > WXK_START) { + return key_code; + } + int uc = event.GetUnicodeKey(); + if (uc != WXK_NONE) { + if (uc < 32) { // not all control chars + switch (uc) { + case WXK_BACK: + case WXK_TAB: + case WXK_RETURN: + case WXK_ESCAPE: + return uc; + default: + return WXK_NONE; + } + } + return uc; + } else { + return event.GetKeyCode(); + } +} + +} // namespace + void Viewer::CloseDlg(wxCloseEvent& ev) { (void)ev; // unused params diff --git a/src/wx/widgets/sdl-poller.cpp b/src/wx/widgets/sdl-poller.cpp new file mode 100644 index 00000000..f85ff350 --- /dev/null +++ b/src/wx/widgets/sdl-poller.cpp @@ -0,0 +1,439 @@ +#include "wx/widgets/sdl-poller.h" + +#include +#include + +#include +#include +#include + +#include + +#include "wx/config/option-id.h" +#include "wx/config/option-observer.h" +#include "wx/config/option-proxy.h" +#include "wx/config/option.h" +#include "wx/config/user-input.h" +#include "wx/widgets/user-input-event.h" + +namespace widgets { + +namespace { + +enum class JoyAxisStatus { Neutral = 0, Plus, Minus }; + +JoyAxisStatus AxisValueToStatus(const int16_t& x) { + if (x > 0x1fff) + return JoyAxisStatus::Plus; + if (x < -0x1fff) + return JoyAxisStatus::Minus; + return JoyAxisStatus::Neutral; +} + +config::JoyControl AxisStatusToJoyControl(const JoyAxisStatus& status) { + switch (status) { + case JoyAxisStatus::Plus: + return config::JoyControl::AxisPlus; + case JoyAxisStatus::Minus: + return config::JoyControl::AxisMinus; + case JoyAxisStatus::Neutral: + default: + // This should never happen. + assert(false); + return config::JoyControl::AxisPlus; + } +} + +config::JoyControl HatStatusToJoyControl(const uint8_t status) { + switch (status) { + case SDL_HAT_UP: + return config::JoyControl::HatNorth; + case SDL_HAT_DOWN: + return config::JoyControl::HatSouth; + case SDL_HAT_LEFT: + return config::JoyControl::HatWest; + case SDL_HAT_RIGHT: + return config::JoyControl::HatEast; + default: + // This should never happen. + assert(false); + return config::JoyControl::HatNorth; + } +} + +} // namespace + +// 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 JoyState : public wxTimer { +public: + JoyState(bool enable_game_controller, int sdl_index); + ~JoyState() override; + + // Move constructor and assignment. + JoyState(JoyState&& other); + JoyState& operator=(JoyState&& other); + + // Disable copy constructor and assignment. + JoyState(const JoyState&) = delete; + JoyState& operator=(const JoyState&) = delete; + + // Returns true if this object was properly initialized. + bool IsValid() const; + + // Returns true if this object is a game controller. + bool is_game_controller() const { return !!game_controller_; } + + // Processes the corresponding events. + std::vector ProcessAxisEvent(const uint8_t index, const JoyAxisStatus status); + std::vector ProcessButtonEvent(const uint8_t index, const bool pressed); + std::vector ProcessHatEvent(const uint8_t index, const uint8_t status); + + // 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 abstraction for UI events. + config::JoyId wx_joystick_; + + // 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* sdl_joystick_ = nullptr; + + // Current state of Joystick axis. + std::unordered_map axis_{}; + + // Current state of Joystick buttons. + std::unordered_map buttons_{}; + + // Current state of Joystick HAT. Unused for GameControllers. + std::unordered_map hats_{}; + + // Set to true to activate joystick rumble. + bool rumbling_ = false; +}; + +JoyState::JoyState(bool enable_game_controller, int sdl_index) : wx_joystick_(sdl_index) { + if (enable_game_controller && SDL_IsGameController(sdl_index)) { + game_controller_ = SDL_GameControllerOpen(sdl_index); + if (game_controller_) + sdl_joystick_ = SDL_GameControllerGetJoystick(game_controller_); + } else { + sdl_joystick_ = SDL_JoystickOpen(sdl_index); + } + + if (!sdl_joystick_) + return; + + joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_); +} + +JoyState::~JoyState() { + // Nothing to do if this object is not initialized. + if (!sdl_joystick_) + return; + + if (game_controller_) { + SDL_GameControllerClose(game_controller_); + } else { + SDL_JoystickClose(sdl_joystick_); + } +} + +JoyState::JoyState(JoyState&& other) : wx_joystick_(other.wx_joystick_) { + if (this == &other) { + return; + } + + sdl_joystick_ = other.sdl_joystick_; + game_controller_ = other.game_controller_; + joystick_id_ = other.joystick_id_; + axis_ = std::move(other.axis_); + buttons_ = std::move(other.buttons_); + hats_ = std::move(other.hats_); + rumbling_ = other.rumbling_; + + other.sdl_joystick_ = nullptr; + other.game_controller_ = nullptr; +} + +JoyState& JoyState::operator=(JoyState&& other) { + if (this == &other) { + return *this; + } + + sdl_joystick_ = other.sdl_joystick_; + game_controller_ = other.game_controller_; + joystick_id_ = other.joystick_id_; + axis_ = std::move(other.axis_); + buttons_ = std::move(other.buttons_); + hats_ = std::move(other.hats_); + rumbling_ = other.rumbling_; + + other.sdl_joystick_ = nullptr; + other.game_controller_ = nullptr; + + return *this; +} + +bool JoyState::IsValid() const { + return sdl_joystick_; +} + +std::vector JoyState::ProcessAxisEvent(const uint8_t index, + const JoyAxisStatus status) { + const JoyAxisStatus previous_status = axis_[index]; + std::vector events; + + // Nothing to do if no-op. + if (status == previous_status) { + return events; + } + + // Update the value. + axis_[index] = status; + + if (previous_status != JoyAxisStatus::Neutral) { + // Send the "unpressed" event. + events.push_back(UserInputEvent( + config::UserInput(index, AxisStatusToJoyControl(previous_status), wx_joystick_), + false)); + } + + // We already sent the "unpressed" event so nothing more to do. + if (status == JoyAxisStatus::Neutral) { + return events; + } + + // Send the "pressed" event. + events.push_back(UserInputEvent( + config::UserInput(index, AxisStatusToJoyControl(status), wx_joystick_), true)); + + return events; +} + +std::vector JoyState::ProcessButtonEvent(const uint8_t index, const bool status) { + const bool previous_status = buttons_[index]; + std::vector events; + + // Nothing to do if no-op. + if (status == previous_status) { + return events; + } + + // Update the value. + buttons_[index] = status; + + // Send the event. + events.push_back( + UserInputEvent(config::UserInput(index, config::JoyControl::Button, wx_joystick_), status)); + + return events; +} + +std::vector JoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) { + const uint16_t previous_status = hats_[index]; + std::vector events; + + // Nothing to do if no-op. + if (status == previous_status) { + return events; + } + + // Update the value. + hats_[index] = status; + + // For HATs, the status value is a bit field, where each bit corresponds to + // a direction. These are parsed here to send the corresponding "pressed" + // and "unpressed" events. + for (uint8_t bit = 0x01; bit != 0x10; bit <<= 1) { + const bool old_control_pressed = (previous_status & bit) != 0; + const bool new_control_pressed = (status & bit) != 0; + if (old_control_pressed && !new_control_pressed) { + // Send the "unpressed" event. + events.push_back(UserInputEvent( + config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), false)); + } + if (!old_control_pressed && new_control_pressed) { + // Send the "pressed" event. + events.push_back(UserInputEvent( + config::UserInput(index, HatStatusToJoyControl(bit), wx_joystick_), true)); + } + } + + return events; +} + +void JoyState::SetRumble(bool activate_rumble) { + rumbling_ = activate_rumble; + +#if SDL_VERSION_ATLEAST(2, 0, 9) + if (!game_controller_) + return; + + if (rumbling_) { + SDL_GameControllerRumble(game_controller_, 0xFFFF, 0xFFFF, 300); + if (!IsRunning()) { + Start(150); + } + } else { + SDL_GameControllerRumble(game_controller_, 0, 0, 0); + Stop(); + } +#endif +} + +void JoyState::Notify() { + SetRumble(rumbling_); +} + +SdlPoller::SdlPoller(const EventHandlerProvider handler_provider) + : enable_game_controller_(OPTION(kSDLGameControllerMode)), + handler_provider_(handler_provider), + game_controller_enabled_observer_( + config::OptionID::kSDLGameControllerMode, + [this](config::Option* option) { ReconnectControllers(option->GetBool()); }) { + wxTimer::Start(50); + SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS); + SDL_GameControllerEventState(SDL_ENABLE); + SDL_JoystickEventState(SDL_ENABLE); +} + +SdlPoller::~SdlPoller() { + wxTimer::Stop(); + SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_Quit(); +} + +void SdlPoller::SetRumble(bool rumble) { + if (rumble) { + if (!joystick_states_.empty()) { + auto it = joystick_states_.begin(); + it->second.SetRumble(true); + } + } else { + if (!joystick_states_.empty()) { + auto it = joystick_states_.begin(); + it->second.SetRumble(false); + } + } +} + +void SdlPoller::ReconnectControllers(bool enable_game_controller) { + enable_game_controller_ = enable_game_controller; + RemapControllers(); +} + +void SdlPoller::Notify() { + SDL_Event sdl_event; + + while (SDL_PollEvent(&sdl_event)) { + std::vector events; + JoyState* joy_state = nullptr; + switch (sdl_event.type) { + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + joy_state = FindJoyState(sdl_event.cbutton.which); + if (joy_state) { + events = joy_state->ProcessButtonEvent(sdl_event.cbutton.button, + sdl_event.cbutton.state); + } + break; + + case SDL_CONTROLLERAXISMOTION: + joy_state = FindJoyState(sdl_event.caxis.which); + if (joy_state) { + events = joy_state->ProcessAxisEvent(sdl_event.caxis.axis, + AxisValueToStatus(sdl_event.caxis.value)); + } + 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: + joy_state = FindJoyState(sdl_event.jbutton.which); + if (joy_state && !joy_state->is_game_controller()) { + events = joy_state->ProcessButtonEvent(sdl_event.jbutton.button, + sdl_event.jbutton.state); + } + break; + + case SDL_JOYAXISMOTION: + joy_state = FindJoyState(sdl_event.jaxis.which); + if (joy_state && !joy_state->is_game_controller()) { + events = joy_state->ProcessAxisEvent(sdl_event.jaxis.axis, + AxisValueToStatus(sdl_event.jaxis.value)); + } + break; + + case SDL_JOYHATMOTION: + joy_state = FindJoyState(sdl_event.jhat.which); + if (joy_state && !joy_state->is_game_controller()) { + events = joy_state->ProcessHatEvent(sdl_event.jhat.hat, sdl_event.jhat.value); + } + break; + + case SDL_JOYDEVICEADDED: + // Always remap all controllers. + RemapControllers(); + break; + + case SDL_JOYDEVICEREMOVED: + joystick_states_.erase(sdl_event.jdevice.which); + break; + } + + if (!events.empty()) { + wxEvtHandler* handler = handler_provider_(); + if (handler) { + for (const auto& user_input_event : events) { + handler->QueueEvent(user_input_event.Clone()); + } + } + } + } +} + +JoyState* SdlPoller::FindJoyState(const SDL_JoystickID& joy_id) { + auto it = joystick_states_.find(joy_id); + if (it == joystick_states_.end()) { + return nullptr; + } + + return &it->second; +} + +void SdlPoller::RemapControllers() { + // Clear the current joystick states. + joystick_states_.clear(); + + // Reconnect all controllers. + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + JoyState joy_state(enable_game_controller_, i); + if (joy_state.IsValid()) { + joystick_states_.insert({joy_state.joystick_id(), std::move(joy_state)}); + } + } +} + +} // namespace widgets diff --git a/src/wx/widgets/sdl-poller.h b/src/wx/widgets/sdl-poller.h new file mode 100644 index 00000000..a375bc09 --- /dev/null +++ b/src/wx/widgets/sdl-poller.h @@ -0,0 +1,70 @@ +#ifndef WX_WIDGETS_SDL_POLLER_H_ +#define WX_WIDGETS_SDL_POLLER_H_ + +#include +#include + +#include + +#include + +#include "wx/config/option-observer.h" + +// Forward declarations. +class wxEvtHandler; + +namespace widgets { + +// Forward declaration. +class JoyState; + +// Provider for the event handler to send the events to. +using EventHandlerProvider = std::function; + +// The SDL worker is responsible for handling SDL events and firing the +// appropriate `UserInputEvent` for joysticks. +// It is used to fire `UserInputEvent` for joysticks. This class should be kept +// as a singleton owned by the application object. +class SdlPoller final : public wxTimer { +public: + explicit SdlPoller(const EventHandlerProvider handler_provider); + ~SdlPoller() final; + + // Disable copy and copy assignment. + SdlPoller(const SdlPoller&) = delete; + SdlPoller& operator=(const SdlPoller&) = delete; + + // Sets or unsets the controller rumble. + void SetRumble(bool rumble); + +private: + // Helper method to find a joystick state from a joystick ID. + // Returns nullptr if not present. Called from the SDL worker thread. + JoyState* FindJoyState(const SDL_JoystickID& joy_id); + + // Remap all controllers. Called from the SDL worker thread. + void RemapControllers(); + + // Reconnects all controllers. + void ReconnectControllers(bool enable_game_controller); + + // wxTimer implementation. + void Notify() final; + + // Map of SDL joystick ID to joystick state. Only contains active joysticks. + // Only accessed from the SDL worker thread. + std::map joystick_states_; + + // Set to true to enable the game controller API. + bool enable_game_controller_ = false; + + // The provider of event handlers to send the events to. + const EventHandlerProvider handler_provider_; + + // Observer for the game controller enabled option. + const config::OptionsObserver game_controller_enabled_observer_; +}; + +} // namespace widgets + +#endif // WX_WIDGETS_SDL_POLLER_H_ diff --git a/src/wx/widgets/sdljoy.cpp b/src/wx/widgets/sdljoy.cpp deleted file mode 100644 index 3f655e4a..00000000 --- a/src/wx/widgets/sdljoy.cpp +++ /dev/null @@ -1,493 +0,0 @@ -#include "wx/widgets/sdljoy.h" - -#include -#include - -#include "wx/config/option-proxy.h" -#include "wx/config/option.h" -#include "wx/wxvbam.h" - -namespace { - -enum class wxAxisStatus { - Neutral = 0, - Plus, - Minus -}; - -wxAxisStatus AxisValueToStatus(const int16_t& x) { - if (x > 0x1fff) - return wxAxisStatus::Plus; - if (x < -0x1fff) - return wxAxisStatus::Minus; - return wxAxisStatus::Neutral; -} - -wxJoyControl AxisStatusToJoyControl(const wxAxisStatus& status) { - switch (status) { - case wxAxisStatus::Plus: - return wxJoyControl::AxisPlus; - case wxAxisStatus::Minus: - return wxJoyControl::AxisMinus; - case wxAxisStatus::Neutral: - default: - // This should never happen. - assert(false); - return wxJoyControl::AxisPlus; - } -} - -wxJoyControl HatStatusToJoyControl(const uint8_t status) { - switch (status) { - case SDL_HAT_UP: - return wxJoyControl::HatNorth; - case SDL_HAT_DOWN: - return wxJoyControl::HatSouth; - case SDL_HAT_LEFT: - return wxJoyControl::HatWest; - case SDL_HAT_RIGHT: - return wxJoyControl::HatEast; - default: - // This should never happen. - assert(false); - return wxJoyControl::HatNorth; - } -} - -} // namespace - -// For testing a GameController as a Joystick: -//#define SDL_IsGameController(x) false - -// static -wxJoystick wxJoystick::Invalid() { - return wxJoystick(kInvalidSdlIndex); -} - -// static -wxJoystick wxJoystick::FromLegacyPlayerIndex(unsigned player_index) { - assert(player_index != 0); - return wxJoystick(player_index - 1); -} - -wxString wxJoystick::ToString() { - return wxString::Format("Joy%d", sdl_index_ + 1); -} - -bool wxJoystick::operator==(const wxJoystick& other) const { - return sdl_index_ == other.sdl_index_; -} -bool wxJoystick::operator!=(const wxJoystick& other) const { - return !(*this == other); -} -bool wxJoystick::operator<(const wxJoystick& other) const { - return sdl_index_ < other.sdl_index_; -} -bool wxJoystick::operator<=(const wxJoystick& other) const { - return !(*this > other); -} -bool wxJoystick::operator>(const wxJoystick& other) const { - return other < *this; -} -bool wxJoystick::operator>=(const wxJoystick& other) const { - return !(*this < other); -} - -wxJoystick::wxJoystick(int sdl_index) : sdl_index_(sdl_index) {} - -wxJoyEvent::wxJoyEvent( - wxJoystick joystick, - wxJoyControl control, - uint8_t control_index, - bool pressed) : - wxCommandEvent(wxEVT_JOY), - joystick_(joystick), - control_(control), - control_index_(control_index), - pressed_(pressed) {} - -wxDEFINE_EVENT(wxEVT_JOY, wxJoyEvent); - -// 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); - explicit wxSDLJoyState(wxJoystick joystick); - ~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; - - // Returns true if this object is a game controller. - bool is_game_controller() const { return !!game_controller_; } - - // Processes the corresponding events. - void ProcessAxisEvent(const uint8_t index, const wxAxisStatus status); - void ProcessButtonEvent(const uint8_t index, const bool pressed); - void ProcessHatEvent(const uint8_t index, const uint8_t status); - - // 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 abstraction for UI events. - wxJoystick wx_joystick_; - - // 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* sdl_joystick_ = nullptr; - - // Current state of Joystick axis. - std::unordered_map axis_{}; - - // Current state of Joystick buttons. - std::unordered_map buttons_{}; - - // Current state of Joystick HAT. Unused for GameControllers. - std::unordered_map hats_{}; - - // Set to true to activate joystick rumble. - bool rumbling_ = false; -}; - -wxSDLJoyState::wxSDLJoyState(int sdl_index) - : wxSDLJoyState(wxJoystick(sdl_index)) {} - -wxSDLJoyState::wxSDLJoyState(wxJoystick joystick) : wx_joystick_(joystick) { - int sdl_index = wx_joystick_.sdl_index_; - if (OPTION(kSDLGameControllerMode) && SDL_IsGameController(sdl_index)) { - game_controller_ = SDL_GameControllerOpen(sdl_index); - if (game_controller_) - sdl_joystick_ = SDL_GameControllerGetJoystick(game_controller_); - } else { - sdl_joystick_ = SDL_JoystickOpen(sdl_index); - } - - if (!sdl_joystick_) - return; - - joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_); - systemScreenMessage( - wxString::Format(_("Connected %s: %s"), - wx_joystick_.ToString(), SDL_JoystickNameForIndex(sdl_index))); -} - -wxSDLJoyState::~wxSDLJoyState() { - // Nothing to do if this object is not initialized. - if (!sdl_joystick_) - return; - - if (game_controller_) - SDL_GameControllerClose(game_controller_); - else - SDL_JoystickClose(sdl_joystick_); - - systemScreenMessage( - wxString::Format(_("Disconnected %s"), wx_joystick_.ToString())); -} - -bool wxSDLJoyState::IsValid() const { - return sdl_joystick_; -} - -void wxSDLJoyState::ProcessAxisEvent(const uint8_t index, const wxAxisStatus status) { - auto handler = wxGetApp().frame->GetJoyEventHandler(); - const wxAxisStatus previous_status = axis_[index]; - - // Nothing to do if no-op. - if (status == previous_status) { - return; - } - - // Update the value. - axis_[index] = status; - - // Nothing more to do if we can't send events. - if (!handler) { - return; - } - - wxLogDebug("Got Axis motion: %s ctrl_idx:%d val:%d prev_val:%d", - wx_joystick_.ToString(), index, status, previous_status); - - if (previous_status != wxAxisStatus::Neutral) { - // Send the "unpressed" event. - wxQueueEvent(handler, new wxJoyEvent( - wx_joystick_, - AxisStatusToJoyControl(previous_status), - index, - false)); - } - - // We already sent the "unpressed" event so nothing more to do. - if (status == wxAxisStatus::Neutral) { - return; - } - - // Send the "pressed" event. - wxQueueEvent(handler, new wxJoyEvent( - wx_joystick_, - AxisStatusToJoyControl(status), - index, - true)); - -} - -void wxSDLJoyState::ProcessButtonEvent(const uint8_t index, const bool status) { - auto handler = wxGetApp().frame->GetJoyEventHandler(); - const bool previous_status = buttons_[index]; - - // Nothing to do if no-op. - if (status == previous_status) { - return; - } - - // Update the value. - buttons_[index] = status; - - // Nothing more to do if we can't send events. - if (!handler) { - return; - } - - wxLogDebug("Got Button event: %s ctrl_idx:%d val:%d prev_val:%d", - wx_joystick_.ToString(), index, status, previous_status); - - // Send the event. - wxQueueEvent(handler, new wxJoyEvent( - wx_joystick_, wxJoyControl::Button, index, status)); -} - -void wxSDLJoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) { - auto handler = wxGetApp().frame->GetJoyEventHandler(); - const uint16_t previous_status = hats_[index]; - - // Nothing to do if no-op. - if (status == previous_status) { - return; - } - - // Update the value. - hats_[index] = status; - - // Nothing more to do if we can't send events. - if (!handler) { - return; - } - - wxLogDebug("Got Hat event: %s ctrl_idx:%d val:%d prev_val:%d", - wx_joystick_.ToString(), index, status, previous_status); - - // For HATs, the status value is a bit field, where each bit corresponds to - // a direction. These are parsed here to send the corresponding "pressed" - // and "unpressed" events. - for (uint8_t bit = 0x01; bit != 0x10; bit <<= 1) { - const bool old_control_pressed = (previous_status & bit) != 0; - const bool new_control_pressed = (status & bit) != 0; - if (old_control_pressed && !new_control_pressed) { - // Send the "unpressed" event. - wxQueueEvent(handler, new wxJoyEvent( - wx_joystick_, HatStatusToJoyControl(bit), index, false)); - } - if (!old_control_pressed && new_control_pressed) { - // Send the "pressed" event. - wxQueueEvent(handler, new wxJoyEvent( - wx_joystick_, HatStatusToJoyControl(bit), index, true)); - - } - } -} - -void wxSDLJoyState::SetRumble(bool activate_rumble) { - rumbling_ = activate_rumble; - -#if SDL_VERSION_ATLEAST(2, 0, 9) - if (!game_controller_) - return; - - if (rumbling_) { - SDL_GameControllerRumble(game_controller_, 0xFFFF, 0xFFFF, 300); - if (!IsRunning()) - Start(150); - } else { - SDL_GameControllerRumble(game_controller_, 0, 0, 0); - Stop(); - } -#endif -} - -void wxSDLJoyState::Notify() { - SetRumble(rumbling_); -} - -wxJoyPoller::wxJoyPoller() { - // Start up joystick if not already started - // FIXME: check for errors - SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); - SDL_GameControllerEventState(SDL_ENABLE); - SDL_JoystickEventState(SDL_ENABLE); -} - -wxJoyPoller::~wxJoyPoller() { - // It is necessary to free all SDL resources before quitting SDL. - joystick_states_.clear(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); -} - -void wxJoyPoller::Poll() { - SDL_Event e; - wxSDLJoyState* joy_state = nullptr; - - while (SDL_PollEvent(&e)) { - switch (e.type) { - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - joy_state = FindJoyState(e.cbutton.which); - if (joy_state) { - joy_state->ProcessButtonEvent( - e.cbutton.button, e.cbutton.state); - } - break; - - case SDL_CONTROLLERAXISMOTION: - joy_state = FindJoyState(e.caxis.which); - if (joy_state) { - joy_state->ProcessAxisEvent( - e.caxis.axis, AxisValueToStatus(e.caxis.value)); - } - 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: - joy_state = FindJoyState(e.jbutton.which); - if (joy_state && !joy_state->is_game_controller()) { - joy_state->ProcessButtonEvent( - e.jbutton.button, e.jbutton.state); - } - break; - - case SDL_JOYAXISMOTION: - joy_state = FindJoyState(e.jaxis.which); - if (joy_state && !joy_state->is_game_controller()) { - joy_state->ProcessAxisEvent( - e.jaxis.axis, AxisValueToStatus(e.jaxis.value)); - } - break; - - case SDL_JOYHATMOTION: - joy_state = FindJoyState(e.jhat.which); - if (joy_state && !joy_state->is_game_controller()) { - joy_state->ProcessHatEvent( - e.jhat.hat, e.jhat.value); - } - break; - - case SDL_JOYDEVICEADDED: - // Always remap all controllers. - RemapControllers(); - break; - - case SDL_JOYDEVICEREMOVED: - joystick_states_.erase(e.jdevice.which); - break; - } - } -} - -void wxJoyPoller::RemapControllers() { - if (!is_polling_active_) { - // Nothing to do when we're not actively polling. - return; - } - - joystick_states_.clear(); - - if (requested_joysticks_.empty()) { - // Connect all joysticks. - for (int i = 0; i < SDL_NumJoysticks(); i++) { - std::unique_ptr 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 wxJoystick& joystick : requested_joysticks_) { - std::unique_ptr joy_state( - new wxSDLJoyState(joystick)); - if (joy_state->IsValid()) { - joystick_states_.emplace( - joy_state->joystick_id(), std::move(joy_state)); - } - } - } -} - -wxSDLJoyState* wxJoyPoller::FindJoyState(const SDL_JoystickID& joy_id) { - const auto iter = joystick_states_.find(joy_id); - if (iter == joystick_states_.end()) - return nullptr; - return iter->second.get(); -} - -void wxJoyPoller::PollJoysticks(std::set joysticks) { - // Reset the polling state. - StopPolling(); - - if (joysticks.empty()) { - // Nothing to poll. Return early. - return; - } - - is_polling_active_ = true; - requested_joysticks_ = joysticks; - RemapControllers(); -} - -void wxJoyPoller::PollAllJoysticks() { - // Reset the polling state. - StopPolling(); - is_polling_active_ = true; - RemapControllers(); -} - -void wxJoyPoller::StopPolling() { - joystick_states_.clear(); - requested_joysticks_.clear(); - is_polling_active_ = false; -} - -void wxJoyPoller::SetRumble(bool activate_rumble) { - if (joystick_states_.empty()) - return; - - // Do rumble only on the first device. - joystick_states_.begin()->second->SetRumble(activate_rumble); -} diff --git a/src/wx/widgets/sdljoy.h b/src/wx/widgets/sdljoy.h deleted file mode 100644 index ff034300..00000000 --- a/src/wx/widgets/sdljoy.h +++ /dev/null @@ -1,156 +0,0 @@ -#ifndef VBAM_WX_WIDGETS_SDLJOY_H_ -#define VBAM_WX_WIDGETS_SDLJOY_H_ - -#include -#include -#include - -#include -#include - -#include -#include -#include - -// Forward declarations. -class wxSDLJoyState; -class wxJoyPoller; - -// The different types of supported controls. -enum wxJoyControl { - AxisPlus = 0, - AxisMinus, - Button, - HatNorth, - HatSouth, - HatWest, - HatEast, - Last = HatEast -}; - -// Abstraction for a single joystick. In the current implementation, this -// encapsulates an |sdl_index_|. Creation of these objects should only be -// handled by wxSDLJoyState, with the exception of the legacy player_index -// constructor, which should eventually go away. -class wxJoystick { -public: - static wxJoystick Invalid(); - - // TODO: Remove this constructor once the transition to the new UserInput - // type is complete. - static wxJoystick FromLegacyPlayerIndex(unsigned player_index); - - virtual ~wxJoystick() = default; - - wxString ToString(); - - // TODO: Remove this API once the transition to the new UserInput type is - // complete. - unsigned player_index() { return sdl_index_ + 1; } - - bool operator==(const wxJoystick& other) const; - bool operator!=(const wxJoystick& other) const; - bool operator<(const wxJoystick& other) const; - bool operator<=(const wxJoystick& other) const; - bool operator>(const wxJoystick& other) const; - bool operator>=(const wxJoystick& other) const; - -private: - static const int kInvalidSdlIndex = -1; - friend class wxSDLJoyState; - - wxJoystick() = delete; - wxJoystick(int sdl_index); - - int sdl_index_; -}; - -// Represents a Joystick event. -class wxJoyEvent : public wxCommandEvent { -public: - wxJoyEvent( - wxJoystick joystick, - wxJoyControl control, - uint8_t control_index, - bool pressed); - virtual ~wxJoyEvent() = default; - - wxJoystick joystick() const { return joystick_; } - wxJoyControl control() const { return control_; } - uint8_t control_index() const { return control_index_; } - bool pressed() const { return pressed_; } - -private: - wxJoystick joystick_; - wxJoyControl control_; - uint8_t control_index_; - bool pressed_; -}; - -wxDECLARE_EVENT(wxEVT_JOY, wxJoyEvent); - -typedef void (wxEvtHandler::*wxJoyEventFunction)(wxJoyEvent&); -#define wxJoyEventHandler(func) wxEVENT_HANDLER_CAST(wxJoyEventFunction, func) -#define EVT_SDLJOY(func) \ - wx__DECLARE_EVT0(wxEVT_JOY, wxJoyEventHandler(func)) - -// 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 wxJoyEvent. -// Handling of the wxJoystick 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 wxJoystick value is -// sent to the UI during input event configuration. -class wxJoyPoller { -public: - wxJoyPoller(); - ~wxJoyPoller(); - - // Adds a set of joysticks to the list of polled joysticks. - // This will disconnect every active joysticks, and reactivates the ones - // matching an index in |joysticks|. Missing joysticks will be connected if - // they connect later on. - void PollJoysticks(std::set joysticks); - - // 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(); - -private: - // Reconnects all controllers. - void RemapControllers(); - - // Helper method to find a joystick state from a joystick ID. - // Returns nullptr if not present. - wxSDLJoyState* FindJoyState(const SDL_JoystickID& joy_id); - - // Map of SDL joystick ID to joystick state. Only contains active joysticks. - std::unordered_map> joystick_states_; - - // Set of requested SDL joystick indexes. - std::set requested_joysticks_; - - // Set to true when we are actively polling controllers. - bool is_polling_active_ = false; - - // Timestamp when the latest poll was done. - wxLongLong last_poll_ = wxGetUTCTimeMillis(); -}; - -#endif // VBAM_WX_WIDGETS_SDLJOY_H_ diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp index 80df46b3..9af57691 100644 --- a/src/wx/widgets/user-input-ctrl.cpp +++ b/src/wx/widgets/user-input-ctrl.cpp @@ -1,35 +1,13 @@ #include "wx/widgets/user-input-ctrl.h" +#include + #include "wx/config/user-input.h" #include "wx/opts.h" +#include "wx/widgets/user-input-event.h" namespace widgets { -namespace { - -int FilterKeyCode(const wxKeyEvent& event) { - const wxChar keycode = event.GetUnicodeKey(); - if (keycode == WXK_NONE) { - return event.GetKeyCode(); - } - - if (keycode < 32) { - switch (keycode) { - case WXK_BACK: - case WXK_TAB: - case WXK_RETURN: - case WXK_ESCAPE: - return keycode; - default: - return WXK_NONE; - } - } - - return keycode; -} - -} // namespace - extern const char UserInputCtrlNameStr[] = "userinputctrl"; UserInputCtrl::UserInputCtrl() : wxTextCtrl() {} @@ -53,6 +31,13 @@ bool UserInputCtrl::Create(wxWindow* parent, const wxSize& size, long style, const wxString& name) { + this->SetClientObject(new UserInputEventSender(this)); + this->Bind(VBAM_EVT_USER_INPUT_UP, &UserInputCtrl::OnUserInputUp, this); + this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { + is_navigating_away_ = false; + last_focus_time_ = wxGetUTCTimeMillis(); + event.Skip(); + }); return wxTextCtrl::Create(parent, id, value, pos, size, style, wxValidator(), name); } @@ -81,28 +66,18 @@ void UserInputCtrl::Clear() { wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl); -// clang-format off -wxBEGIN_EVENT_TABLE(UserInputCtrl, wxTextCtrl) - EVT_SDLJOY(UserInputCtrl::OnJoy) - EVT_KEY_DOWN(UserInputCtrl::OnKeyDown) - EVT_KEY_UP(UserInputCtrl::OnKeyUp) -wxEND_EVENT_TABLE(); -// clang-format on - -void UserInputCtrl::OnJoy(wxJoyEvent& event) { - static wxLongLong last_event = 0; - - // Filter consecutive axis motions within 300ms, as this adds two bindings - // +1/-1 instead of the one intended. - if ((event.control() == wxJoyControl::AxisPlus || event.control() == wxJoyControl::AxisMinus) && - wxGetUTCTimeMillis() - last_event < 300) { +void UserInputCtrl::OnUserInputUp(UserInputEvent& event) { + static const wxLongLong kInterval = 100; + if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) { + // Ignore events sent very shortly after focus. This is used to ignore + // some spurious joystick events like an accidental axis motion. + event.Skip(); return; } - last_event = wxGetUTCTimeMillis(); - - // Control was unpressed, ignore. - if (!event.pressed()) { + if (is_navigating_away_) { + // Ignore events sent after the control has been navigated away from. + event.Skip(); return; } @@ -110,35 +85,10 @@ void UserInputCtrl::OnJoy(wxJoyEvent& event) { inputs_.clear(); } - inputs_.emplace(event); - UpdateText(); - Navigate(); -} - -void UserInputCtrl::OnKeyDown(wxKeyEvent& event) { - last_mod_ = event.GetModifiers(); - last_key_ = FilterKeyCode(event); -} - -void UserInputCtrl::OnKeyUp(wxKeyEvent&) { - const int mod = last_mod_; - const int key = last_key_; - last_mod_ = last_key_ = 0; - - // key is only 0 if we missed the keydown event - // or if we are being shipped pseudo keypress events - // either way, just ignore. - if (!key) { - return; - } - - if (!is_multikey_) { - inputs_.clear(); - } - - inputs_.emplace(key, mod); + inputs_.insert(event.input()); UpdateText(); Navigate(); + is_navigating_away_ = true; } void UserInputCtrl::UpdateText() { @@ -153,6 +103,7 @@ void UserInputCtrl::UpdateText() { if (value.empty()) { SetValue(wxEmptyString); } else { + // Remove the trailing comma. SetValue(value.substr(0, value.size() - 1)); } } diff --git a/src/wx/widgets/user-input-ctrl.h b/src/wx/widgets/user-input-ctrl.h index 95e8fe35..aa046845 100644 --- a/src/wx/widgets/user-input-ctrl.h +++ b/src/wx/widgets/user-input-ctrl.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -10,7 +11,7 @@ #include "wx/config/game-control.h" #include "wx/config/user-input.h" -#include "wx/widgets/sdljoy.h" +#include "wx/widgets/user-input-event.h" namespace widgets { @@ -59,20 +60,23 @@ public: void Clear() override; wxDECLARE_DYNAMIC_CLASS(UserInputCtrl); - wxDECLARE_EVENT_TABLE(); private: - // Event handlers. - void OnJoy(wxJoyEvent& event); - void OnKeyDown(wxKeyEvent& event); - void OnKeyUp(wxKeyEvent& event); + // Event handler. + void OnUserInputUp(widgets::UserInputEvent& event); // Updates the text in the control to reflect the current inputs. void UpdateText(); bool is_multikey_ = false; - int last_mod_ = 0; - int last_key_ = 0; + + // The last time the control was focused. This is used to ignore events sent + // very shortly after activation. + wxLongLong last_focus_time_ = 0; + + // Set to true after one input has been received. This is used to ignore + // subsequent events until the control is focused again. + bool is_navigating_away_ = false; std::set inputs_; }; diff --git a/src/wx/widgets/user-input-event.cpp b/src/wx/widgets/user-input-event.cpp new file mode 100644 index 00000000..ebc1b6ca --- /dev/null +++ b/src/wx/widgets/user-input-event.cpp @@ -0,0 +1,283 @@ +#include "wx/widgets/user-input-event.h" + +#include + +#include +#include +#include + +#include "wx/config/user-input.h" + +namespace widgets { + +namespace { + +// Filters the received key code in the key event for something we can use. +wxKeyCode FilterKeyCode(const wxKeyEvent& event) { + const wxKeyCode unicode_key = static_cast(event.GetUnicodeKey()); + if (unicode_key == WXK_NONE) { + // We need to filter out modifier keys here so we can differentiate + // between a key press and a modifier press. + const wxKeyCode keycode = static_cast(event.GetKeyCode()); + switch (keycode) { + case WXK_CONTROL: + case WXK_ALT: + case WXK_SHIFT: +#ifdef __WXMAC__ + case WXK_RAW_CONTROL: +#endif + return WXK_NONE; + default: + return keycode; + } + } + + if (unicode_key < 32) { + switch (unicode_key) { + case WXK_BACK: + case WXK_TAB: + case WXK_RETURN: + case WXK_ESCAPE: + return unicode_key; + default: + return WXK_NONE; + } + } + + return unicode_key; +} + +// Returns the set of modifiers for the given key event. +std::unordered_set GetModifiers(const wxKeyEvent& event) { + // Standalone modifier are treated as keys and do not set the keyboard modifiers. + switch (event.GetKeyCode()) { + case WXK_CONTROL: + return {wxMOD_CONTROL}; + case WXK_ALT: + return {wxMOD_ALT}; + case WXK_SHIFT: + return {wxMOD_SHIFT}; +#ifdef __WXMAC__ + case WXK_RAW_CONTROL: + return {wxMOD_RAW_CONTROL}; +#endif + } + + std::unordered_set mods; + if (event.ControlDown()) { + mods.insert(wxMOD_CONTROL); + } + if (event.AltDown()) { + mods.insert(wxMOD_ALT); + } + if (event.ShiftDown()) { + mods.insert(wxMOD_SHIFT); + } +#ifdef __WXMAC__ + if (event.RawControlDown()) { + mods.insert(wxMOD_RAW_CONTROL); + } +#endif + return mods; +} + +// Builds a wxKeyModifier from a set of modifiers. +wxKeyModifier GetModifiersFromSet(const std::unordered_set& mods) { + int mod = wxMOD_NONE; + for (const wxKeyModifier m : mods) { + mod |= m; + } + return static_cast(mod); +} + +// Returns the key code for a standalone modifier. +wxKeyCode KeyFromModifier(const wxKeyModifier mod) { + switch (mod) { + case wxMOD_CONTROL: + return WXK_CONTROL; + case wxMOD_ALT: + return WXK_ALT; + case wxMOD_SHIFT: + return WXK_SHIFT; +#ifdef __WXMAC__ + case wxMOD_RAW_CONTROL: + return WXK_RAW_CONTROL; +#endif + default: + return WXK_NONE; + } +} + +} // namespace + +UserInputEvent::UserInputEvent(const config::UserInput& input, bool pressed) + : wxEvent(0, pressed ? VBAM_EVT_USER_INPUT_DOWN : VBAM_EVT_USER_INPUT_UP), input_(input) {} + +wxEvent* UserInputEvent::Clone() const { + return new UserInputEvent(*this); +} + +UserInputEventSender::UserInputEventSender(wxWindow* const window) + : window_(window) { + assert(window); + + // Attach the event handlers. + window_->Bind(wxEVT_KEY_DOWN, &UserInputEventSender::OnKeyDown, this); + window_->Bind(wxEVT_KEY_UP, &UserInputEventSender::OnKeyUp, this); + window_->Bind(wxEVT_SET_FOCUS, &UserInputEventSender::Reset, this, window_->GetId()); + window_->Bind(wxEVT_KILL_FOCUS, &UserInputEventSender::Reset, this, window_->GetId()); +} + +UserInputEventSender::~UserInputEventSender() = default; + +void UserInputEventSender::OnKeyDown(wxKeyEvent& event) { + // Stop propagation of the event. + event.Skip(false); + + const wxKeyCode key = FilterKeyCode(event); + const std::unordered_set mods = GetModifiers(event); + + wxKeyCode key_pressed = WXK_NONE; + if (key != WXK_NONE) { + if (active_keys_.find(key) == active_keys_.end()) { + // Key was not pressed before. + key_pressed = key; + active_keys_.insert(key); + } + } + + wxKeyModifier mod_pressed = wxMOD_NONE; + for (const wxKeyModifier mod : mods) { + if (active_mods_.find(mod) == active_mods_.end()) { + // Mod was not pressed before. + active_mods_.insert(mod); + mod_pressed = mod; + break; + } + } + + if (key_pressed == WXK_NONE && mod_pressed == wxMOD_NONE) { + // No new keys or mods were pressed. + return; + } + + const wxKeyModifier active_mods = GetModifiersFromSet(active_mods_); + std::vector new_inputs; + if (key_pressed == WXK_NONE) { + // A new standalone modifier was pressed, send the event. + new_inputs.emplace_back(KeyFromModifier(mod_pressed), mod_pressed); + } else { + // A new key was pressed, send the event with modifiers, first. + new_inputs.emplace_back(key, active_mods); + + if (active_mods != wxMOD_NONE) { + // Keep track of the key pressed with the active modifiers. + active_mod_inputs_.emplace(key, active_mods); + + // Also send the key press event without modifiers. + new_inputs.emplace_back(key, wxMOD_NONE); + } + } + + for (const config::UserInput& input : new_inputs) { + wxQueueEvent(window_, new UserInputEvent(input, true)); + } +} + +void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { + // Stop propagation of the event. + event.Skip(false); + + const wxKeyCode key = FilterKeyCode(event); + const std::unordered_set mods = GetModifiers(event); + const wxKeyModifier previous_mods = GetModifiersFromSet(active_mods_); + + wxKeyCode key_released = WXK_NONE; + if (key != WXK_NONE) { + auto iter = active_keys_.find(key); + if (iter != active_keys_.end()) { + // Key was pressed before. + key_released = key; + active_keys_.erase(iter); + } + } + + wxKeyModifier mod_released = wxMOD_NONE; + if (key_released == WXK_NONE) { + // Only look for a standalone modifier if no key was released. + for (const wxKeyModifier mod : mods) { + auto iter = active_mods_.find(mod); + if (iter != active_mods_.end()) { + // Mod was pressed before. + mod_released = mod; + active_mods_.erase(iter); + break; + } + } + } + + if (key_released == WXK_NONE && mod_released == wxMOD_NONE) { + // No keys or mods were released. + return; + } + + std::vector released_inputs; + if (key_released == WXK_NONE) { + // A standalone modifier was released, send it. + released_inputs.emplace_back(KeyFromModifier(mod_released), mod_released); + } else { + // A key was released. + if (previous_mods == wxMOD_NONE) { + // The key was pressed without modifiers, just send the key release event. + released_inputs.emplace_back(key, wxMOD_NONE); + } else { + // Check if the key was pressed with the active modifiers. + const config::UserInput input_with_modifiers(key, previous_mods); + auto iter = active_mod_inputs_.find(input_with_modifiers); + if (iter == active_mod_inputs_.end()) { + // The key press event was never sent, so do it now. + wxQueueEvent(window_, new UserInputEvent(input_with_modifiers, true)); + } else { + active_mod_inputs_.erase(iter); + } + + // Send the key release event with the active modifiers. + released_inputs.emplace_back(key, previous_mods); + + // Also send the key release event without modifiers. + released_inputs.emplace_back(key, wxMOD_NONE); + } + } + + // Also check for any key that were pressed with the previously active + // modifiers and release them. + for (const wxKeyCode active_key : active_keys_) { + const config::UserInput input(active_key, previous_mods); + auto iter = active_mod_inputs_.find(input); + if (iter != active_mod_inputs_.end()) { + active_mod_inputs_.erase(iter); + released_inputs.push_back(std::move(input)); + } + } + + + for (const config::UserInput& input : released_inputs) { + active_mod_inputs_.erase(input); + wxQueueEvent(window_, new UserInputEvent(input, false)); + } +} + +void UserInputEventSender::Reset(wxFocusEvent& event) { + // Reset the internal state. + active_keys_.clear(); + active_mods_.clear(); + active_mod_inputs_.clear(); + + // Let the event propagate. + event.Skip(); +} + +} // namespace widgets + +wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); +wxDEFINE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent); diff --git a/src/wx/widgets/user-input-event.h b/src/wx/widgets/user-input-event.h new file mode 100644 index 00000000..ef2e51cc --- /dev/null +++ b/src/wx/widgets/user-input-event.h @@ -0,0 +1,67 @@ +#ifndef WX_WIDGETS_USER_INPUT_EVENT_H_ +#define WX_WIDGETS_USER_INPUT_EVENT_H_ + +#include + +#include +#include + +#include "wx/config/user-input.h" + +namespace widgets { + +// Event fired when a user input is pressed or released. The event contains the +// user input that was pressed or released. +class UserInputEvent final : public wxEvent { +public: + UserInputEvent(const config::UserInput& input, bool pressed); + virtual ~UserInputEvent() override = default; + + // wxEvent implementation. + wxEvent* Clone() const override; + + const config::UserInput& input() const { return input_; } + +private: + const config::UserInput input_; +}; + +// Object that is used to fire user input events when a key is pressed or +// released. To use this object, attach it to a wxWindow and listen for +// the VBAM_EVT_USER_INPUT_DOWN and VBAM_EVT_USER_INPUT_UP events. +class UserInputEventSender final : public wxClientData { +public: + // `window` must not be nullptr. Will assert otherwise. + // `window` must outlive this object. + explicit UserInputEventSender(wxWindow* const window); + ~UserInputEventSender() override; + + // Disable copy and copy assignment. + UserInputEventSender(const UserInputEventSender&) = delete; + UserInputEventSender& operator=(const UserInputEventSender&) = delete; + +private: + // Keyboard event handlers. + void OnKeyDown(wxKeyEvent& event); + void OnKeyUp(wxKeyEvent& event); + + // Resets the internal state. Called on focus in/out. + void Reset(wxFocusEvent& event); + + std::unordered_set active_keys_; + std::unordered_set active_mods_; + std::set active_mod_inputs_; + + // The wxWindow this object is attached to. + // Must outlive this object. + wxWindow* const window_; +}; + +} // namespace widgets + +// Fired when a user input is pressed. +wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_DOWN, widgets::UserInputEvent); +// Fired when a user input is released. +wxDECLARE_EVENT(VBAM_EVT_USER_INPUT_UP, widgets::UserInputEvent); + +#endif // WX_WIDGETS_USER_INPUT_EVENT_H_ diff --git a/src/wx/wxhead.h b/src/wx/wxhead.h index d61b07ea..e4078ca2 100644 --- a/src/wx/wxhead.h +++ b/src/wx/wxhead.h @@ -57,8 +57,6 @@ using std::int32_t; // GetAccel is inefficent anyway (often I don't want to convert to wxAccEnt) // This is a working replacement for SetAccel, at least. -#include "wx/wxutil.h" - // wxrc helpers (for dynamic strings instead of constant) #define XRCID_D(str) wxXmlResource::GetXRCID(str) //#define XRCCTRL_D(win, id, type) (wxStaticCast((win).FindWindow(XRCID_D(id)), type)) diff --git a/src/wx/wxutil.cpp b/src/wx/wxutil.cpp deleted file mode 100644 index 5e84dbd4..00000000 --- a/src/wx/wxutil.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "wx/wxutil.h" - -int getKeyboardKeyCode(const wxKeyEvent& event) { - const int key_code = event.GetKeyCode(); - if (key_code > WXK_START) { - return key_code; - } - int uc = event.GetUnicodeKey(); - if (uc != WXK_NONE) { - if (uc < 32) { // not all control chars - switch (uc) { - case WXK_BACK: - case WXK_TAB: - case WXK_RETURN: - case WXK_ESCAPE: - return uc; - default: - return WXK_NONE; - } - } - return uc; - } else { - return event.GetKeyCode(); - } -} diff --git a/src/wx/wxutil.h b/src/wx/wxutil.h deleted file mode 100644 index 939fada4..00000000 --- a/src/wx/wxutil.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef VBAM_WX_UTIL_H_ -#define VBAM_WX_UTIL_H_ - -#include - -int getKeyboardKeyCode(const wxKeyEvent& event); - -#endif // VBAM_WX_UTIL_H_ diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index a7b2c7b0..461715c9 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -1,14 +1,13 @@ -// mainline: -// parse cmd line -// load xrc file (guiinit.cpp does most of instantiation) -// create & display main frame - #include "wx/wxvbam.h" #ifdef __WXMSW__ #include #endif +#ifdef __WXGTK__ +#include +#endif + #include #include #include @@ -31,18 +30,9 @@ #include #include "components/user_config/user_config.h" -#include "core/gb/gbGlobals.h" #include "core/gba/gbaSound.h" - -#if defined(VBAM_ENABLE_DEBUGGER) -#include "core/gba/gbaRemote.h" -#endif // defined(VBAM_ENABLE_DEBUGGER) - -// The built-in xrc file -#include "wx/builtin-xrc.h" - -// The built-in vba-over.ini #include "wx/builtin-over.h" +#include "wx/builtin-xrc.h" #include "wx/config/game-control.h" #include "wx/config/option-proxy.h" #include "wx/config/option.h" @@ -53,9 +43,9 @@ #include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/utils.h" -#ifdef __WXGTK__ -#include -#endif +#if defined(VBAM_ENABLE_DEBUGGER) +#include "core/gba/gbaRemote.h" +#endif // defined(VBAM_ENABLE_DEBUGGER) namespace { @@ -261,6 +251,13 @@ static void tack_full_path(wxString& s, const wxString& app = wxEmptyString) s += wxT("\n\t") + full_config_path[i] + app; } +wxvbamApp::wxvbamApp() + : wxApp(), + pending_fullscreen(false), + frame(nullptr), + using_wayland(false), + sdl_poller_(widgets::SdlPoller(std::bind(&wxvbamApp::GetJoyEventHandler, this))) {} + const wxString wxvbamApp::GetPluginsDir() { return wxStandardPaths::Get().GetPluginsDir(); @@ -836,6 +833,30 @@ wxvbamApp::~wxvbamApp() { #endif } +wxEvtHandler* wxvbamApp::GetJoyEventHandler() { + // Use the active window, if any. + wxWindow* focused_window = wxWindow::FindFocus(); + if (focused_window) { + return focused_window; + } + + if (!frame) { + return nullptr; + } + + auto panel = frame->GetPanel(); + if (!panel || !panel->panel) { + return nullptr; + } + + if (OPTION(kUIAllowJoystickBackgroundInput)) { + // Use the game panel, if the background polling option is enabled. + return panel->panel->GetWindow()->GetEventHandler(); + } + + return nullptr; +} + MainFrame::MainFrame() : wxFrame(), paused(false), @@ -849,8 +870,6 @@ MainFrame::MainFrame() keep_on_top_styler_(this), status_bar_observer_(config::OptionID::kGenStatusBar, std::bind(&MainFrame::OnStatusBarChanged, this)) { - jpoll = new JoystickPoller(); - this->Connect(wxID_ANY, wxEVT_SHOW, wxShowEventHandler(JoystickPoller::ShowDialog), jpoll, jpoll); } MainFrame::~MainFrame() { @@ -983,19 +1002,15 @@ int MainFrame::FilterEvent(wxEvent& event) { return wxEventFilter::Event_Skip; } - int command = 0; - if (event.GetEventType() == wxEVT_KEY_DOWN) { - const wxKeyEvent& key_event = static_cast(event); - command = gopts.shortcuts.CommandForInput(config::UserInput(key_event)); - } else if (event.GetEventType() == wxEVT_JOY) { - const wxJoyEvent& joy_event = static_cast(event); - if (joy_event.pressed()) { - // We ignore "button up" events here. - command = gopts.shortcuts.CommandForInput(config::UserInput(joy_event)); - } + if (event.GetEventType() != VBAM_EVT_USER_INPUT_DOWN) { + // We only treat "VBAM_EVT_USER_INPUT_DOWN" events here. + return wxEventFilter::Event_Skip; } + const widgets::UserInputEvent& user_input_event = static_cast(event); + const int command = gopts.shortcuts.CommandForInput(user_input_event.input()); if (command == 0) { + // No associated command found. return wxEventFilter::Event_Skip; } @@ -1028,58 +1043,6 @@ wxString MainFrame::GetGamePath(wxString path) return game_path; } -void MainFrame::SetJoystick() -{ - /* Remove all attached joysticks to avoid errors while - * destroying and creating the GameArea `panel`. */ - joy.StopPolling(); - - if (!emulating) - return; - - std::set needed_joysticks = gopts.shortcuts.Joysticks(); - for (const auto& iter : gopts.game_control_bindings) { - for (const auto& input_iter : iter.second) { - needed_joysticks.emplace(input_iter.joystick()); - } - } - joy.PollJoysticks(std::move(needed_joysticks)); -} - -void MainFrame::StopJoyPollTimer() -{ - if (jpoll && jpoll->IsRunning()) - jpoll->Stop(); -} - -void MainFrame::StartJoyPollTimer() -{ - if (jpoll && !jpoll->IsRunning()) - jpoll->Start(); -} - -bool MainFrame::IsJoyPollTimerRunning() -{ - return jpoll->IsRunning(); -} - -wxEvtHandler* MainFrame::GetJoyEventHandler() -{ - auto focused_window = wxWindow::FindFocus(); - - if (focused_window) - return focused_window; - - auto panel = GetPanel(); - if (!panel) - return nullptr; - - if (OPTION(kUIAllowJoystickBackgroundInput)) - return panel->GetEventHandler(); - - return nullptr; -} - void MainFrame::enable_menus() { for (int i = 0; i < ncmds; i++) @@ -1403,7 +1366,8 @@ void MainFrame::IdentifyRom() // grabbing those keys, but I can't track it down. int wxvbamApp::FilterEvent(wxEvent& event) { - if (frame) + if (frame) { return frame->FilterEvent(event); + } return wxApp::FilterEvent(event); } diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 217bd877..db1197ba 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -17,7 +17,8 @@ #include "wx/dialogs/base-dialog.h" #include "wx/widgets/dpi-support.h" #include "wx/widgets/keep-on-top-styler.h" -#include "wx/widgets/sdljoy.h" +#include "wx/widgets/sdl-poller.h" +#include "wx/widgets/user-input-event.h" #include "wx/widgets/wxmisc.h" #include "wx/wxhead.h" @@ -30,7 +31,6 @@ #endif #include "wx/wxlogdebug.h" -#include "wx/wxutil.h" template void CheckPointer(T pointer) @@ -59,13 +59,7 @@ class MainFrame; class wxvbamApp : public wxApp { public: - wxvbamApp() - : wxApp() - , pending_fullscreen(false) - , frame(NULL) - , using_wayland(false) - { - } + wxvbamApp(); virtual bool OnInit(); virtual int OnRun(); virtual bool OnCmdLineHelp(wxCmdLineParser&); @@ -94,6 +88,8 @@ public: // without this, global accels don't always work int FilterEvent(wxEvent&); + widgets::SdlPoller* sdl_poller() { return &sdl_poller_; } + // vba-over.ini wxFileConfig* overrides = nullptr; @@ -130,9 +126,14 @@ protected: int console_status = 0; private: + // Returns the currently active event handler to use for user input events. + wxEvtHandler* GetJoyEventHandler(); + wxPathList config_path; char* home = nullptr; + widgets::SdlPoller sdl_poller_; + // Main configuration file. wxFileName config_file_; }; @@ -216,8 +217,6 @@ public: void GetMenuOptionBool(const wxString& menuName, bool* field); void SetMenuOption(const wxString& menuName, bool value); - void SetJoystick(); - int FilterEvent(wxEvent& event); GameArea* GetPanel() @@ -308,24 +307,11 @@ public: virtual bool DialogOpened() { return dialog_opened != 0; } - virtual void SetJoystickRumble(bool b) { joy.SetRumble(b); } - bool IsPaused(bool incendental = false) { return (paused && !pause_next && !incendental) || dialog_opened; } - void PollJoysticks() { joy.Poll(); } - - void PollAllJoysticks() { joy.PollAllJoysticks(); } - - // Poll joysticks with timer. - void StartJoyPollTimer(); - void StopJoyPollTimer(); - bool IsJoyPollTimerRunning(); - - wxEvtHandler* GetJoyEventHandler(); - // required for building from xrc DECLARE_DYNAMIC_CLASS(MainFrame); // required for event handling @@ -351,9 +337,6 @@ private: checkable_mi_array_t checkable_mi; // recent menu item accels wxMenu* recent; - // joystick reader - wxJoyPoller joy; - JoystickPoller* jpoll = nullptr; // quicker & more accurate than FindFocus() != NULL bool focused; // One-time toggle to indicate that this object is fully initialized. This @@ -390,20 +373,6 @@ private: #include "wx/cmdhandlers.h" }; -// a class for polling joystick keys -class JoystickPoller : public wxTimer { - public: - void Notify() { - wxGetApp().frame->PollJoysticks(); - } - void ShowDialog(wxShowEvent& ev) { - if (ev.IsShown()) - Start(50); - else - Stop(); - } -}; - // a helper class to avoid forgetting StopModal() class ModalPause { public: @@ -581,9 +550,8 @@ protected: bool paused; void OnIdle(wxIdleEvent&); - void OnKeyDown(wxKeyEvent& ev); - void OnKeyUp(wxKeyEvent& ev); - void OnSDLJoy(wxJoyEvent& ev); + void OnUserInputDown(widgets::UserInputEvent& event); + void OnUserInputUp(widgets::UserInputEvent& event); void PaintEv(wxPaintEvent& ev); void EraseBackground(wxEraseEvent& ev); void OnSize(wxSizeEvent& ev);