[Input] Unify UserInput event handling

This adds a custom UserInputEvent for handling joypad and keyboard input
for both accelerators and emulator control configuration.

These changes fix a number of issues with the wxWidgets implementation
of key down / up event handling. In particular, every "down" event now
has a corresponding "up" event. All of the special handling that was
done in multiple places to handle shortcuts is now done in a new class,
`UserInputEventSender`, simplifying multiple call sites.

This is another step towards complete unification of UserInput handling,
which will prevent double assignment between shortcuts and emulator
controls.

Bug: #745
This commit is contained in:
Fabrice de Gans 2024-04-22 16:33:54 -07:00 committed by Fabrice de Gans
parent cc65ef2849
commit 1e1a369c8d
28 changed files with 1307 additions and 1229 deletions

View File

@ -78,8 +78,10 @@ set(VBAM_WX_COMMON
widgets/render-plugin.h widgets/render-plugin.h
widgets/user-input-ctrl.cpp widgets/user-input-ctrl.cpp
widgets/user-input-ctrl.h widgets/user-input-ctrl.h
widgets/sdljoy.cpp widgets/user-input-event.cpp
widgets/sdljoy.h widgets/user-input-event.h
widgets/sdl-poller.cpp
widgets/sdl-poller.h
widgets/utils.cpp widgets/utils.cpp
widgets/utils.h widgets/utils.h
widgets/webupdatedef.h widgets/webupdatedef.h
@ -87,8 +89,6 @@ set(VBAM_WX_COMMON
widgets/wxmisc.cpp widgets/wxmisc.cpp
wxhead.h wxhead.h
wxlogdebug.h wxlogdebug.h
wxutil.cpp
wxutil.h
wxvbam.cpp wxvbam.cpp
wxvbam.h wxvbam.h
x11keymap.h x11keymap.h

View File

@ -547,17 +547,17 @@ wxThread::ExitCode BackgroundInput::CheckKeyboard()
// virtual key "i" is pressed // virtual key "i" is pressed
if ((bits & 0x8000) && (previousState[i] & 0x8000) == 0) { if ((bits & 0x8000) && (previousState[i] & 0x8000) == 0) {
if (handler && !wxWindow::FindFocus()) { if (handler && !wxWindow::FindFocus()) {
wxKeyEvent ev(wxEVT_KEY_DOWN); wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_DOWN);
ev.m_keyCode = xKeySym; event->m_keyCode = xKeySym;
handler->AddPendingEvent(ev); handler->QueueEvent(event);
} }
} }
// virtual key "i" is released // virtual key "i" is released
else if (((bits & 0x8000) == 0) && (previousState[i] & 0x8000)) { else if (((bits & 0x8000) == 0) && (previousState[i] & 0x8000)) {
if (handler && !wxWindow::FindFocus()) { if (handler && !wxWindow::FindFocus()) {
wxKeyEvent ev(wxEVT_KEY_UP); wxKeyEvent* event = new wxKeyEvent(wxEVT_KEY_UP);
ev.m_keyCode = xKeySym; event->m_keyCode = xKeySym;
handler->AddPendingEvent(ev); handler->QueueEvent(event);
} }
} }
previousState[i] = bits; previousState[i] = bits;

View File

@ -1184,7 +1184,7 @@ EVT_HANDLER(AllowKeyboardBackgroundInput, "Allow keyboard background input (togg
disableKeyboardBackgroundInput(); disableKeyboardBackgroundInput();
if (OPTION(kUIAllowKeyboardBackgroundInput)) { if (OPTION(kUIAllowKeyboardBackgroundInput)) {
if (panel && panel->panel) { 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...") 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) { if (ShowModal(GetXRCDialog("JoypadConfig")) == wxID_OK) {
update_joypad_opts(); update_joypad_opts();
} }
if (!joy_timer) {
frame->StopJoyPollTimer();
}
SetJoystick();
} }
EVT_HANDLER(Customize, "Customize UI...") EVT_HANDLER(Customize, "Customize UI...")
{ {
wxDialog* dlg = GetXRCDialog("AccelConfig"); if (ShowModal(GetXRCDialog("AccelConfig")) == wxID_OK) {
joy.PollAllJoysticks();
auto frame = wxGetApp().frame;
bool joy_timer = frame->IsJoyPollTimerRunning();
if (!joy_timer) frame->StartJoyPollTimer();
if (ShowModal(dlg) == wxID_OK) {
update_shortcut_opts(); update_shortcut_opts();
ResetMenuAccelerators(); ResetMenuAccelerators();
} }
if (!joy_timer) frame->StopJoyPollTimer();
SetJoystick();
} }
#ifndef NO_ONLINEUPDATES #ifndef NO_ONLINEUPDATES

View File

@ -2,7 +2,6 @@
#include "wx/opts.h" #include "wx/opts.h"
#include "wx/strutils.h" #include "wx/strutils.h"
#include "wx/wxlogdebug.h"
namespace config { namespace config {

View File

@ -1,6 +1,7 @@
#ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_ #ifndef VBAM_WX_CONFIG_GAME_CONTROL_H_
#define VBAM_WX_CONFIG_GAME_CONTROL_H_ #define VBAM_WX_CONFIG_GAME_CONTROL_H_
#include <cstdint>
#include <array> #include <array>
#include <map> #include <map>
#include <set> #include <set>

View File

@ -87,16 +87,6 @@ int Shortcuts::CommandForInput(const UserInput& input) const {
return iter->second; return iter->second;
} }
std::set<wxJoystick> Shortcuts::Joysticks() const {
std::set<wxJoystick> 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 { Shortcuts Shortcuts::Clone() const {
return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_); return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_);
} }

View File

@ -9,7 +9,7 @@
#include "wx/config/user-input.h" #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) #define wxID_FILE10 (wxID_FILE9 + 1)
namespace config { namespace config {
@ -45,9 +45,6 @@ public:
// Returns the command currently assigned to `input` or nullptr if none. // Returns the command currently assigned to `input` or nullptr if none.
int CommandForInput(const UserInput& input) const; int CommandForInput(const UserInput& input) const;
// Returns the set of joysticks used by shortcuts.
std::set<wxJoystick> Joysticks() const;
// Returns a copy of this object. This can be an expensive operation and // Returns a copy of this object. This can be an expensive operation and
// should only be used to modify the currently active shortcuts // should only be used to modify the currently active shortcuts
// configuration. // configuration.

View File

@ -8,7 +8,6 @@
#include <wx/translation.h> #include <wx/translation.h>
#include "wx/strutils.h" #include "wx/strutils.h"
#include "wx/wxutil.h"
namespace config { namespace config {
@ -202,26 +201,26 @@ UserInput StringToUserInput(const wxString& string) {
if (kAxisRegex.Matches(remainder)) { if (kAxisRegex.Matches(remainder)) {
kAxisRegex.GetMatch(&start, &length, 1); kAxisRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); const int key = StringToInt(remainder.Mid(start, length));
const int mod = const JoyControl control =
remainder[start + length] == '+' ? wxJoyControl::AxisPlus : wxJoyControl::AxisMinus; remainder[start + length] == '+' ? JoyControl::AxisPlus : JoyControl::AxisMinus;
return UserInput(key, mod, joy); return UserInput(key, control, JoyId(joy - 1));
} }
if (kButtonRegex.Matches(remainder)) { if (kButtonRegex.Matches(remainder)) {
kButtonRegex.GetMatch(&start, &length, 1); kButtonRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); 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)) { if (kHatRegex.Matches(remainder)) {
kHatRegex.GetMatch(&start, &length, 1); kHatRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length)); const int key = StringToInt(remainder.Mid(start, length));
if (kHatRegex.GetMatch(remainder, 3).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()) { } 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()) { } 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()) { } 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 } // namespace
UserInput::UserInput(const wxKeyEvent& event) // static
: UserInput(Device::Keyboard, event.GetModifiers(), getKeyboardKeyCode(event), 0) {} 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, : UserInput(Device::Joystick,
event.control(), static_cast<int>(control),
event.control_index(), control_index,
event.joystick().player_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 // static
std::set<UserInput> UserInput::FromConfigString(const wxString& string) { std::set<UserInput> UserInput::FromConfigString(const wxString& string) {
@ -315,26 +347,26 @@ wxString UserInput::ToConfigString() const {
return KeyboardInputToConfigString(mod_, key_); return KeyboardInputToConfigString(mod_, key_);
case Device::Joystick: case Device::Joystick:
wxString key; wxString key;
switch (mod_) { switch (static_cast<JoyControl>(mod_)) {
case wxJoyControl::AxisPlus: case JoyControl::AxisPlus:
key = wxString::Format(("Axis%d+"), key_); key = wxString::Format(("Axis%d+"), key_);
break; break;
case wxJoyControl::AxisMinus: case JoyControl::AxisMinus:
key = wxString::Format(("Axis%d-"), key_); key = wxString::Format(("Axis%d-"), key_);
break; break;
case wxJoyControl::Button: case JoyControl::Button:
key = wxString::Format(("Button%d"), key_); key = wxString::Format(("Button%d"), key_);
break; break;
case wxJoyControl::HatNorth: case JoyControl::HatNorth:
key = wxString::Format(("Hat%dN"), key_); key = wxString::Format(("Hat%dN"), key_);
break; break;
case wxJoyControl::HatSouth: case JoyControl::HatSouth:
key = wxString::Format(("Hat%dS"), key_); key = wxString::Format(("Hat%dS"), key_);
break; break;
case wxJoyControl::HatWest: case JoyControl::HatWest:
key = wxString::Format(("Hat%dW"), key_); key = wxString::Format(("Hat%dW"), key_);
break; break;
case wxJoyControl::HatEast: case JoyControl::HatEast:
key = wxString::Format(("Hat%dE"), key_); key = wxString::Format(("Hat%dE"), key_);
break; break;
} }

View File

@ -1,14 +1,56 @@
#ifndef VBAM_WX_CONFIG_USER_INPUT_H_ #ifndef VBAM_WX_CONFIG_USER_INPUT_H_
#define VBAM_WX_CONFIG_USER_INPUT_H_ #define VBAM_WX_CONFIG_USER_INPUT_H_
#include <wx/event.h> #include <cstdint>
#include <wx/string.h>
#include <set> #include <set>
#include "wx/widgets/sdljoy.h" #include <wx/event.h>
#include <wx/log.h>
#include <wx/string.h>
namespace config { 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. // 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 // This class implements comparison operators so it can be used in sets and as
// a key in maps. // a key in maps.
@ -34,14 +76,15 @@ public:
// Invalid UserInput, mainly used for comparison. // Invalid UserInput, mainly used for comparison.
UserInput() : UserInput(Device::Invalid, 0, 0, 0) {} UserInput() : UserInput(Device::Invalid, 0, 0, 0) {}
// Constructor from a wxKeyEvent. // Constructor for a joystick input.
UserInput(const wxKeyEvent& event); UserInput(uint8_t control_index, JoyControl control, JoyId joystick);
// Constructor from a wxJoyEvent. // Constructors for a keyboard input.
UserInput(const wxJoyEvent& event); UserInput(wxKeyCode key, wxKeyModifier mod = wxMOD_NONE);
UserInput(char c, wxKeyModifier mod = wxMOD_NONE);
// TODO: Remove this once all uses have been removed. // 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, : UserInput(joy == 0 ? Device::Keyboard : Device::Joystick,
mod, mod,
key, key,
@ -55,14 +98,27 @@ public:
// Converts to a localized string for display. // Converts to a localized string for display.
wxString ToLocalizedString() const; wxString ToLocalizedString() const;
wxJoystick joystick() const { return joystick_; } JoyId joystick() const { return joystick_; }
constexpr bool is_valid() const { return device_ != Device::Invalid; } constexpr bool is_valid() const { return device_ != Device::Invalid; }
constexpr operator bool() const { return is_valid(); } 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 key() const { return key_; }
int mod() const { return mod_; } int mod() const { return mod_; }
unsigned joy() const { return joy_; } unsigned joy() const { return joy_; }
JoyControl joy_control() const {
assert(is_joystick());
return static_cast<JoyControl>(mod_);
}
wxKeyCode key_code() const {
assert(is_keyboard());
return static_cast<wxKeyCode>(key_);
}
constexpr bool operator==(const UserInput& other) const { constexpr bool operator==(const UserInput& other) const {
return device_ == other.device_ && mod_ == other.mod_ && return device_ == other.device_ && mod_ == other.mod_ &&
key_ == other.key_ && joy_ == other.joy_; key_ == other.key_ && joy_ == other.joy_;
@ -98,14 +154,14 @@ public:
private: private:
UserInput(Device device, int mod, int key, unsigned joy) UserInput(Device device, int mod, int key, unsigned joy)
: device_(device), : device_(device),
joystick_(joy == 0 ? wxJoystick::Invalid() joystick_(joy == 0 ? JoyId::Invalid()
: wxJoystick::FromLegacyPlayerIndex(joy)), : JoyId(joy - 1)),
mod_(mod), mod_(mod),
key_(key), key_(key),
joy_(joy) {} joy_(joy) {}
Device device_; Device device_;
wxJoystick joystick_; JoyId joystick_;
int mod_; int mod_;
int key_; int key_;
unsigned joy_; unsigned joy_;

View File

@ -1,13 +1,13 @@
#include "wx/dialogs/joypad-config.h" #include "wx/dialogs/joypad-config.h"
#include <wx/xrc/xmlres.h> #include <wx/checkbox.h>
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
#include "wx/dialogs/base-dialog.h" #include "wx/dialogs/base-dialog.h"
#include "wx/opts.h"
#include "wx/widgets/option-validator.h" #include "wx/widgets/option-validator.h"
#include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/user-input-ctrl.h"
#include "wx/widgets/utils.h" #include "wx/widgets/utils.h"
#include "wx/wxvbam.h"
namespace dialogs { namespace dialogs {
@ -87,7 +87,6 @@ void JoypadConfig::ToggleSDLGameControllerMode() {
OPTION(kSDLGameControllerMode) = OPTION(kSDLGameControllerMode) =
GetValidatedChild<wxCheckBox>("SDLGameControllerMode")->IsChecked(); GetValidatedChild<wxCheckBox>("SDLGameControllerMode")->IsChecked();
ClearAllJoypads(); ClearAllJoypads();
wxGetApp().frame->PollAllJoysticks();
} }
void JoypadConfig::ClearAllJoypads() { void JoypadConfig::ClearAllJoypads() {

View File

@ -2,6 +2,7 @@
#define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_ #define VBAM_WX_DIALOGS_JOYPAD_CONFIG_H_
#include "wx/dialogs/base-dialog.h" #include "wx/dialogs/base-dialog.h"
namespace dialogs { namespace dialogs {
// Manages the Joypad configuration dialog. // Manages the Joypad configuration dialog.
@ -24,7 +25,7 @@ private:
// Clears all Joypad controls for all Joypads. // Clears all Joypad controls for all Joypads.
void ClearAllJoypads(); void ClearAllJoypads();
// Toggle SDL GameController mode for all joysticks. // Toggle SDL GameController mode for all joysticks.
void ToggleSDLGameControllerMode(); void ToggleSDLGameControllerMode();
}; };

View File

@ -105,142 +105,151 @@ uint32_t LoadUnsignedOption(wxConfigBase* cfg,
} // namespace } // namespace
#define WJKB config::UserInput
opts_t gopts; opts_t gopts;
const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBindings = { const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBindings = {
{ config::GameControl(0, config::GameKey::Up), { {config::GameControl(0, config::GameKey::Up),
WJKB('W'), {
WJKB(11, wxJoyControl::Button, 1), config::UserInput('W'),
WJKB(1, wxJoyControl::AxisMinus, 1), config::UserInput(11, config::JoyControl::Button, config::JoyId(0)),
WJKB(3, wxJoyControl::AxisMinus, 1), config::UserInput(1, config::JoyControl::AxisMinus, config::JoyId(0)),
}}, config::UserInput(3, config::JoyControl::AxisMinus, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::Down), { }},
WJKB('S'), {config::GameControl(0, config::GameKey::Down),
WJKB(12, wxJoyControl::Button, 1), {
WJKB(1, wxJoyControl::AxisPlus, 1), config::UserInput('S'),
WJKB(3, wxJoyControl::AxisPlus, 1), config::UserInput(12, config::JoyControl::Button, config::JoyId(0)),
}}, config::UserInput(1, config::JoyControl::AxisPlus, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::Left), { config::UserInput(3, config::JoyControl::AxisPlus, config::JoyId(0)),
WJKB('A'), }},
WJKB(13, wxJoyControl::Button, 1), {config::GameControl(0, config::GameKey::Left),
WJKB(0, wxJoyControl::AxisMinus, 1), {
WJKB(2, wxJoyControl::AxisMinus, 1), config::UserInput('A'),
}}, config::UserInput(13, config::JoyControl::Button, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::Right), { config::UserInput(0, config::JoyControl::AxisMinus, config::JoyId(0)),
WJKB('D'), config::UserInput(2, config::JoyControl::AxisMinus, config::JoyId(0)),
WJKB(14, wxJoyControl::Button, 1), }},
WJKB(0, wxJoyControl::AxisPlus, 1), {config::GameControl(0, config::GameKey::Right),
WJKB(2, wxJoyControl::AxisPlus, 1), {
}}, config::UserInput('D'),
{ config::GameControl(0, config::GameKey::A), { config::UserInput(14, config::JoyControl::Button, config::JoyId(0)),
WJKB('L'), config::UserInput(0, config::JoyControl::AxisPlus, config::JoyId(0)),
WJKB(0, wxJoyControl::Button, 1), config::UserInput(2, config::JoyControl::AxisPlus, config::JoyId(0)),
}}, }},
{ config::GameControl(0, config::GameKey::B), { {config::GameControl(0, config::GameKey::A),
WJKB('K'), {
WJKB(1, wxJoyControl::Button, 1), config::UserInput('L'),
}}, config::UserInput(0, config::JoyControl::Button, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::L), { }},
WJKB('I'), {config::GameControl(0, config::GameKey::B),
WJKB(2, wxJoyControl::Button, 1), {
WJKB(9, wxJoyControl::Button, 1), config::UserInput('K'),
WJKB(4, wxJoyControl::AxisPlus, 1), config::UserInput(1, config::JoyControl::Button, config::JoyId(0)),
}}, }},
{ config::GameControl(0, config::GameKey::R), { {config::GameControl(0, config::GameKey::L),
WJKB('O'), {
WJKB(3, wxJoyControl::Button, 1), config::UserInput('I'),
WJKB(10, wxJoyControl::Button, 1), config::UserInput(2, config::JoyControl::Button, config::JoyId(0)),
WJKB(5, wxJoyControl::AxisPlus, 1), config::UserInput(9, config::JoyControl::Button, config::JoyId(0)),
}}, config::UserInput(4, config::JoyControl::AxisPlus, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::Select), { }},
WJKB(WXK_BACK), {config::GameControl(0, config::GameKey::R),
WJKB(4, wxJoyControl::Button, 1), {
}}, config::UserInput('O'),
{ config::GameControl(0, config::GameKey::Start), { config::UserInput(3, config::JoyControl::Button, config::JoyId(0)),
WJKB(WXK_RETURN), config::UserInput(10, config::JoyControl::Button, config::JoyId(0)),
WJKB(6, wxJoyControl::Button, 1), config::UserInput(5, config::JoyControl::AxisPlus, config::JoyId(0)),
}}, }},
{ config::GameControl(0, config::GameKey::MotionUp), {}}, {config::GameControl(0, config::GameKey::Select),
{ config::GameControl(0, config::GameKey::MotionDown), {}}, {
{ config::GameControl(0, config::GameKey::MotionLeft), {}}, config::UserInput(WXK_BACK),
{ config::GameControl(0, config::GameKey::MotionRight), {}}, config::UserInput(4, config::JoyControl::Button, config::JoyId(0)),
{ config::GameControl(0, config::GameKey::MotionIn), {}}, }},
{ config::GameControl(0, config::GameKey::MotionOut), {}}, {config::GameControl(0, config::GameKey::Start),
{ config::GameControl(0, config::GameKey::AutoA), {}}, {
{ config::GameControl(0, config::GameKey::AutoB), {}}, config::UserInput(WXK_RETURN),
{ config::GameControl(0, config::GameKey::Speed), { config::UserInput(6, config::JoyControl::Button, config::JoyId(0)),
WJKB(WXK_SPACE), }},
}}, {config::GameControl(0, config::GameKey::MotionUp), {}},
{ config::GameControl(0, config::GameKey::Capture), {}}, {config::GameControl(0, config::GameKey::MotionDown), {}},
{ config::GameControl(0, config::GameKey::Gameshark), {}}, {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::Up), {}},
{ config::GameControl(1, config::GameKey::Down), {}}, {config::GameControl(1, config::GameKey::Down), {}},
{ config::GameControl(1, config::GameKey::Left), {}}, {config::GameControl(1, config::GameKey::Left), {}},
{ config::GameControl(1, config::GameKey::Right), {}}, {config::GameControl(1, config::GameKey::Right), {}},
{ config::GameControl(1, config::GameKey::A), {}}, {config::GameControl(1, config::GameKey::A), {}},
{ config::GameControl(1, config::GameKey::B), {}}, {config::GameControl(1, config::GameKey::B), {}},
{ config::GameControl(1, config::GameKey::L), {}}, {config::GameControl(1, config::GameKey::L), {}},
{ config::GameControl(1, config::GameKey::R), {}}, {config::GameControl(1, config::GameKey::R), {}},
{ config::GameControl(1, config::GameKey::Select), {}}, {config::GameControl(1, config::GameKey::Select), {}},
{ config::GameControl(1, config::GameKey::Start), {}}, {config::GameControl(1, config::GameKey::Start), {}},
{ config::GameControl(1, config::GameKey::MotionUp), {}}, {config::GameControl(1, config::GameKey::MotionUp), {}},
{ config::GameControl(1, config::GameKey::MotionDown), {}}, {config::GameControl(1, config::GameKey::MotionDown), {}},
{ config::GameControl(1, config::GameKey::MotionLeft), {}}, {config::GameControl(1, config::GameKey::MotionLeft), {}},
{ config::GameControl(1, config::GameKey::MotionRight), {}}, {config::GameControl(1, config::GameKey::MotionRight), {}},
{ config::GameControl(1, config::GameKey::MotionIn), {}}, {config::GameControl(1, config::GameKey::MotionIn), {}},
{ config::GameControl(1, config::GameKey::MotionOut), {}}, {config::GameControl(1, config::GameKey::MotionOut), {}},
{ config::GameControl(1, config::GameKey::AutoA), {}}, {config::GameControl(1, config::GameKey::AutoA), {}},
{ config::GameControl(1, config::GameKey::AutoB), {}}, {config::GameControl(1, config::GameKey::AutoB), {}},
{ config::GameControl(1, config::GameKey::Speed), {}}, {config::GameControl(1, config::GameKey::Speed), {}},
{ config::GameControl(1, config::GameKey::Capture), {}}, {config::GameControl(1, config::GameKey::Capture), {}},
{ config::GameControl(1, config::GameKey::Gameshark), {}}, {config::GameControl(1, config::GameKey::Gameshark), {}},
{ config::GameControl(2, config::GameKey::Up), {}}, {config::GameControl(2, config::GameKey::Up), {}},
{ config::GameControl(2, config::GameKey::Down), {}}, {config::GameControl(2, config::GameKey::Down), {}},
{ config::GameControl(2, config::GameKey::Left), {}}, {config::GameControl(2, config::GameKey::Left), {}},
{ config::GameControl(2, config::GameKey::Right), {}}, {config::GameControl(2, config::GameKey::Right), {}},
{ config::GameControl(2, config::GameKey::A), {}}, {config::GameControl(2, config::GameKey::A), {}},
{ config::GameControl(2, config::GameKey::B), {}}, {config::GameControl(2, config::GameKey::B), {}},
{ config::GameControl(2, config::GameKey::L), {}}, {config::GameControl(2, config::GameKey::L), {}},
{ config::GameControl(2, config::GameKey::R), {}}, {config::GameControl(2, config::GameKey::R), {}},
{ config::GameControl(2, config::GameKey::Select), {}}, {config::GameControl(2, config::GameKey::Select), {}},
{ config::GameControl(2, config::GameKey::Start), {}}, {config::GameControl(2, config::GameKey::Start), {}},
{ config::GameControl(2, config::GameKey::MotionUp), {}}, {config::GameControl(2, config::GameKey::MotionUp), {}},
{ config::GameControl(2, config::GameKey::MotionDown), {}}, {config::GameControl(2, config::GameKey::MotionDown), {}},
{ config::GameControl(2, config::GameKey::MotionLeft), {}}, {config::GameControl(2, config::GameKey::MotionLeft), {}},
{ config::GameControl(2, config::GameKey::MotionRight), {}}, {config::GameControl(2, config::GameKey::MotionRight), {}},
{ config::GameControl(2, config::GameKey::MotionIn), {}}, {config::GameControl(2, config::GameKey::MotionIn), {}},
{ config::GameControl(2, config::GameKey::MotionOut), {}}, {config::GameControl(2, config::GameKey::MotionOut), {}},
{ config::GameControl(2, config::GameKey::AutoA), {}}, {config::GameControl(2, config::GameKey::AutoA), {}},
{ config::GameControl(2, config::GameKey::AutoB), {}}, {config::GameControl(2, config::GameKey::AutoB), {}},
{ config::GameControl(2, config::GameKey::Speed), {}}, {config::GameControl(2, config::GameKey::Speed), {}},
{ config::GameControl(2, config::GameKey::Capture), {}}, {config::GameControl(2, config::GameKey::Capture), {}},
{ config::GameControl(2, config::GameKey::Gameshark), {}}, {config::GameControl(2, config::GameKey::Gameshark), {}},
{ config::GameControl(3, config::GameKey::Up), {}}, {config::GameControl(3, config::GameKey::Up), {}},
{ config::GameControl(3, config::GameKey::Down), {}}, {config::GameControl(3, config::GameKey::Down), {}},
{ config::GameControl(3, config::GameKey::Left), {}}, {config::GameControl(3, config::GameKey::Left), {}},
{ config::GameControl(3, config::GameKey::Right), {}}, {config::GameControl(3, config::GameKey::Right), {}},
{ config::GameControl(3, config::GameKey::A), {}}, {config::GameControl(3, config::GameKey::A), {}},
{ config::GameControl(3, config::GameKey::B), {}}, {config::GameControl(3, config::GameKey::B), {}},
{ config::GameControl(3, config::GameKey::L), {}}, {config::GameControl(3, config::GameKey::L), {}},
{ config::GameControl(3, config::GameKey::R), {}}, {config::GameControl(3, config::GameKey::R), {}},
{ config::GameControl(3, config::GameKey::Select), {}}, {config::GameControl(3, config::GameKey::Select), {}},
{ config::GameControl(3, config::GameKey::Start), {}}, {config::GameControl(3, config::GameKey::Start), {}},
{ config::GameControl(3, config::GameKey::MotionUp), {}}, {config::GameControl(3, config::GameKey::MotionUp), {}},
{ config::GameControl(3, config::GameKey::MotionDown), {}}, {config::GameControl(3, config::GameKey::MotionDown), {}},
{ config::GameControl(3, config::GameKey::MotionLeft), {}}, {config::GameControl(3, config::GameKey::MotionLeft), {}},
{ config::GameControl(3, config::GameKey::MotionRight), {}}, {config::GameControl(3, config::GameKey::MotionRight), {}},
{ config::GameControl(3, config::GameKey::MotionIn), {}}, {config::GameControl(3, config::GameKey::MotionIn), {}},
{ config::GameControl(3, config::GameKey::MotionOut), {}}, {config::GameControl(3, config::GameKey::MotionOut), {}},
{ config::GameControl(3, config::GameKey::AutoA), {}}, {config::GameControl(3, config::GameKey::AutoA), {}},
{ config::GameControl(3, config::GameKey::AutoB), {}}, {config::GameControl(3, config::GameKey::AutoB), {}},
{ config::GameControl(3, config::GameKey::Speed), {}}, {config::GameControl(3, config::GameKey::Speed), {}},
{ config::GameControl(3, config::GameKey::Capture), {}}, {config::GameControl(3, config::GameKey::Capture), {}},
{ config::GameControl(3, config::GameKey::Gameshark), {}}, {config::GameControl(3, config::GameKey::Gameshark), {}},
}; };
// This constructor only works with globally allocated gopts. // This constructor only works with globally allocated gopts.

View File

@ -1,10 +1,8 @@
#include "wx/wxvbam.h"
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#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__ #ifdef __WXGTK__
#include <X11/Xlib.h> #include <X11/Xlib.h>
@ -22,13 +20,14 @@
#include <wx/dcbuffer.h> #include <wx/dcbuffer.h>
#include <wx/menu.h> #include <wx/menu.h>
#include <SDL_joystick.h>
#include "background-input.h"
#include "components/draw_text/draw_text.h" #include "components/draw_text/draw_text.h"
#include "components/filters/filters.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/file_util.h"
#include "core/base/patch.h" #include "core/base/patch.h"
#include "core/base/system.h"
#include "core/base/version.h" #include "core/base/version.h"
#include "core/gb/gb.h" #include "core/gb/gb.h"
#include "core/gb/gbCheats.h" #include "core/gb/gbCheats.h"
@ -41,15 +40,15 @@
#include "core/gba/gbaPrint.h" #include "core/gba/gbaPrint.h"
#include "core/gba/gbaRtc.h" #include "core/gba/gbaRtc.h"
#include "core/gba/gbaSound.h" #include "core/gba/gbaSound.h"
#include "wx/background-input.h"
#include "wx/config/game-control.h" #include "wx/config/game-control.h"
#include "wx/config/option-id.h"
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
#include "wx/config/option.h" #include "wx/config/option.h"
#include "wx/config/user-input.h"
#include "wx/drawing.h" #include "wx/drawing.h"
#include "wx/wayland.h" #include "wx/wayland.h"
#include "wx/widgets/render-plugin.h" #include "wx/widgets/render-plugin.h"
#include "wx/wxutil.h" #include "wx/widgets/user-input-event.h"
#include "wx/wxvbam.h"
#ifdef __WXMSW__ #ifdef __WXMSW__
#include <windows.h> #include <windows.h>
@ -165,6 +164,7 @@ GameArea::GameArea()
config::OptionID::kSoundBuffers, config::OptionID::kSoundDSoundHWAccel, config::OptionID::kSoundBuffers, config::OptionID::kSoundDSoundHWAccel,
config::OptionID::kSoundUpmix}, config::OptionID::kSoundUpmix},
[&](config::Option*) { schedule_audio_restart_ = true; }) { [&](config::Option*) { schedule_audio_restart_ = true; }) {
this->SetClientObject(new widgets::UserInputEventSender(this));
SetSizer(new wxBoxSizer(wxVERTICAL)); SetSizer(new wxBoxSizer(wxVERTICAL));
// all renderers prefer 32-bit // all renderers prefer 32-bit
// well, "simple" prefers 24-bit, but that's not available for filters // 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; was_paused = true;
schedule_audio_restart_ = false; schedule_audio_restart_ = false;
MainFrame* mf = wxGetApp().frame; MainFrame* mf = wxGetApp().frame;
mf->StopJoyPollTimer();
mf->SetJoystick();
mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA); mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA);
mf->cmd_enable |= ONLOAD_CMDEN; mf->cmd_enable |= ONLOAD_CMDEN;
mf->cmd_enable |= loaded == IMAGE_GB ? CMDEN_GB : (CMDEN_GBA | CMDEN_NGDB_GBA); 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->cmd_enable &= UNLOAD_CMDEN_KEEP;
mf->update_state_ts(true); mf->update_state_ts(true);
mf->enable_menus(); mf->enable_menus();
mf->StartJoyPollTimer();
mf->SetJoystick();
mf->ResetCheatSearch(); mf->ResetCheatSearch();
if (rewind_mem) if (rewind_mem)
@ -1087,8 +1083,6 @@ void GameArea::Pause()
if (loaded != IMAGE_UNKNOWN) if (loaded != IMAGE_UNKNOWN)
soundPause(); soundPause();
wxGetApp().frame->StartJoyPollTimer();
} }
void GameArea::Resume() void GameArea::Resume()
@ -1103,8 +1097,6 @@ void GameArea::Resume()
if (loaded != IMAGE_UNKNOWN) if (loaded != IMAGE_UNKNOWN)
soundResume(); soundResume();
wxGetApp().frame->StopJoyPollTimer();
SetFocus(); SetFocus();
} }
@ -1184,31 +1176,32 @@ void GameArea::OnIdle(wxIdleEvent& event)
wxWindow* w = panel->GetWindow(); wxWindow* w = panel->GetWindow();
// set up event handlers // set up event handlers
w->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this); w->Bind(VBAM_EVT_USER_INPUT_DOWN, &GameArea::OnUserInputDown, this);
w->Connect(wxEVT_KEY_UP, wxKeyEventHandler(GameArea::OnKeyUp), NULL, this); w->Bind(VBAM_EVT_USER_INPUT_UP, &GameArea::OnUserInputUp, this);
w->Connect(wxEVT_PAINT, wxPaintEventHandler(GameArea::PaintEv), NULL, this); w->Bind(wxEVT_PAINT, &GameArea::PaintEv, this);
w->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(GameArea::EraseBackground), NULL, this); w->Bind(wxEVT_ERASE_BACKGROUND, &GameArea::EraseBackground, this);
// set userdata so we know it's the panel and not the frame being resized // set userdata so we know it's the panel and not the frame being resized
// the userdata is freed on disconnect/destruction // 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. // 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.. // Update mouse last-used timers on mouse events etc..
w->Connect(wxEVT_MOTION, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); w->Bind(wxEVT_MOTION, &GameArea::MouseEvent, this);
w->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); w->Bind(wxEVT_LEFT_DOWN, &GameArea::MouseEvent, this);
w->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); w->Bind(wxEVT_RIGHT_DOWN, &GameArea::MouseEvent, this);
w->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); w->Bind(wxEVT_MIDDLE_DOWN, &GameArea::MouseEvent, this);
w->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(GameArea::MouseEvent), NULL, this); w->Bind(wxEVT_MOUSEWHEEL, &GameArea::MouseEvent, this);
w->SetBackgroundStyle(wxBG_STYLE_CUSTOM); w->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
w->SetSize(wxSize(basic_width, basic_height)); w->SetSize(wxSize(basic_width, basic_height));
// Allow input while on background // Allow input while on background
if (OPTION(kUIAllowKeyboardBackgroundInput)) if (OPTION(kUIAllowKeyboardBackgroundInput)) {
enableKeyboardBackgroundInput(w); enableKeyboardBackgroundInput(w->GetEventHandler());
}
if (gopts.max_scale) if (gopts.max_scale)
w->SetMaxSize(wxSize(basic_width * gopts.max_scale, w->SetMaxSize(wxSize(basic_width * gopts.max_scale,
@ -1251,8 +1244,6 @@ void GameArea::OnIdle(wxIdleEvent& event)
UpdateLcdFilter(); UpdateLcdFilter();
} }
mf->PollJoysticks();
if (!paused) { if (!paused) {
HidePointer(); HidePointer();
HideMenuBar(); 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) { static void draw_black_background(wxWindow* win) {
wxClientDC dc(win); wxClientDC dc(win);
wxCoord w, h; wxCoord w, h;
@ -1340,70 +1323,32 @@ static void draw_black_background(wxWindow* win) {
dc.DrawRectangle(0, 0, w, h); 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<wxKeyCode>(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__ #ifdef __WXGTK__
static Display* GetX11Display() static Display* GetX11Display() {
{
return GDK_WINDOW_XDISPLAY(gtk_widget_get_window(wxGetApp().frame->GetHandle())); return GDK_WINDOW_XDISPLAY(gtk_widget_get_window(wxGetApp().frame->GetHandle()));
} }
#endif // __WXGTK__ #endif // __WXGTK__
void GameArea::OnKeyDown(wxKeyEvent& ev) void GameArea::OnUserInputDown(widgets::UserInputEvent& event) {
{ if (config::GameControlState::Instance().OnInputPressed(event.input())) {
process_keyboard_event(ev, true); wxWakeUpIdle();
}
} }
void GameArea::OnKeyUp(wxKeyEvent& ev) void GameArea::OnUserInputUp(widgets::UserInputEvent& event) {
{ if (config::GameControlState::Instance().OnInputReleased(event.input())) {
process_keyboard_event(ev, false); 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 // these three are forwarded to the DrawingPanel instance
@ -1430,24 +1375,8 @@ void GameArea::OnSize(wxSizeEvent& ev)
ev.Skip(); 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) BEGIN_EVENT_TABLE(GameArea, wxPanel)
EVT_IDLE(GameArea::OnIdle) EVT_IDLE(GameArea::OnIdle)
EVT_SDLJOY(GameArea::OnSDLJoy)
// FIXME: wxGTK does not generate motion events in MainFrame (not sure // FIXME: wxGTK does not generate motion events in MainFrame (not sure
// what to do about it) // what to do about it)
EVT_MOUSE_EVENTS(GameArea::MouseEvent) 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(), , wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(),
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{ {
this->SetClientData(new widgets::UserInputEventSender(this));
} }
void DrawingPanelBase::DrawingPanelInit() 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(), , wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(),
wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS)
{ {
this->SetClientData(new widgets::UserInputEventSender(this));
widgets::RequestHighResolutionOpenGlSurfaceForWindow(this); widgets::RequestHighResolutionOpenGlSurfaceForWindow(this);
SetContext(); SetContext();
} }

View File

@ -596,7 +596,7 @@ uint32_t systemGetClock()
void systemCartridgeRumble(bool b) 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) static uint8_t sensorDarkness = 0xE8; // total darkness (including daylight on rainy days)

View File

@ -1,10 +1,38 @@
#include "wx/viewsupt.h" #include "wx/viewsupt.h"
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
#include "wx/wxutil.h"
#include "wx/wxvbam.h" #include "wx/wxvbam.h"
namespace Viewers { 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 Viewer::CloseDlg(wxCloseEvent& ev)
{ {
(void)ev; // unused params (void)ev; // unused params

View File

@ -0,0 +1,439 @@
#include "wx/widgets/sdl-poller.h"
#include <cassert>
#include <map>
#include <SDL_events.h>
#include <wx/timer.h>
#include <wx/toplevel.h>
#include <SDL.h>
#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<UserInputEvent> ProcessAxisEvent(const uint8_t index, const JoyAxisStatus status);
std::vector<UserInputEvent> ProcessButtonEvent(const uint8_t index, const bool pressed);
std::vector<UserInputEvent> 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<uint8_t, JoyAxisStatus> axis_{};
// Current state of Joystick buttons.
std::unordered_map<uint8_t, bool> buttons_{};
// Current state of Joystick HAT. Unused for GameControllers.
std::unordered_map<uint8_t, uint8_t> hats_{};
// Set to true to activate joystick rumble.
bool rumbling_ = false;
};
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<UserInputEvent> JoyState::ProcessAxisEvent(const uint8_t index,
const JoyAxisStatus status) {
const JoyAxisStatus previous_status = axis_[index];
std::vector<UserInputEvent> 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<UserInputEvent> JoyState::ProcessButtonEvent(const uint8_t index, const bool status) {
const bool previous_status = buttons_[index];
std::vector<UserInputEvent> 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<UserInputEvent> JoyState::ProcessHatEvent(const uint8_t index, const uint8_t status) {
const uint16_t previous_status = hats_[index];
std::vector<UserInputEvent> 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<UserInputEvent> 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

View File

@ -0,0 +1,70 @@
#ifndef WX_WIDGETS_SDL_POLLER_H_
#define WX_WIDGETS_SDL_POLLER_H_
#include <functional>
#include <map>
#include <wx/timer.h>
#include <SDL.h>
#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<wxEvtHandler*()>;
// 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<SDL_JoystickID, JoyState> 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_

View File

@ -1,493 +0,0 @@
#include "wx/widgets/sdljoy.h"
#include <wx/timer.h>
#include <SDL.h>
#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<uint8_t, wxAxisStatus> axis_{};
// Current state of Joystick buttons.
std::unordered_map<uint8_t, bool> buttons_{};
// Current state of Joystick HAT. Unused for GameControllers.
std::unordered_map<uint8_t, uint8_t> hats_{};
// Set to true to activate joystick rumble.
bool rumbling_ = false;
};
wxSDLJoyState::wxSDLJoyState(int sdl_index)
: 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<wxSDLJoyState> joy_state(new wxSDLJoyState(i));
if (joy_state->IsValid()) {
joystick_states_.emplace(
joy_state->joystick_id(), std::move(joy_state));
}
}
} else {
// Only attempt to add the joysticks we care about.
for (const wxJoystick& joystick : requested_joysticks_) {
std::unique_ptr<wxSDLJoyState> 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<wxJoystick> 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);
}

View File

@ -1,156 +0,0 @@
#ifndef VBAM_WX_WIDGETS_SDLJOY_H_
#define VBAM_WX_WIDGETS_SDLJOY_H_
#include <memory>
#include <set>
#include <unordered_map>
#include <wx/time.h>
#include <wx/event.h>
#include <SDL_joystick.h>
#include <SDL_gamecontroller.h>
#include <SDL_events.h>
// 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<wxJoystick> 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<SDL_JoystickID, std::unique_ptr<wxSDLJoyState>> joystick_states_;
// Set of requested SDL joystick indexes.
std::set<wxJoystick> 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_

View File

@ -1,35 +1,13 @@
#include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/user-input-ctrl.h"
#include <wx/time.h>
#include "wx/config/user-input.h" #include "wx/config/user-input.h"
#include "wx/opts.h" #include "wx/opts.h"
#include "wx/widgets/user-input-event.h"
namespace widgets { 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"; extern const char UserInputCtrlNameStr[] = "userinputctrl";
UserInputCtrl::UserInputCtrl() : wxTextCtrl() {} UserInputCtrl::UserInputCtrl() : wxTextCtrl() {}
@ -53,6 +31,13 @@ bool UserInputCtrl::Create(wxWindow* parent,
const wxSize& size, const wxSize& size,
long style, long style,
const wxString& name) { 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); return wxTextCtrl::Create(parent, id, value, pos, size, style, wxValidator(), name);
} }
@ -81,28 +66,18 @@ void UserInputCtrl::Clear() {
wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl); wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl);
// clang-format off void UserInputCtrl::OnUserInputUp(UserInputEvent& event) {
wxBEGIN_EVENT_TABLE(UserInputCtrl, wxTextCtrl) static const wxLongLong kInterval = 100;
EVT_SDLJOY(UserInputCtrl::OnJoy) if (wxGetUTCTimeMillis() - last_focus_time_ < kInterval) {
EVT_KEY_DOWN(UserInputCtrl::OnKeyDown) // Ignore events sent very shortly after focus. This is used to ignore
EVT_KEY_UP(UserInputCtrl::OnKeyUp) // some spurious joystick events like an accidental axis motion.
wxEND_EVENT_TABLE(); event.Skip();
// 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) {
return; return;
} }
last_event = wxGetUTCTimeMillis(); if (is_navigating_away_) {
// Ignore events sent after the control has been navigated away from.
// Control was unpressed, ignore. event.Skip();
if (!event.pressed()) {
return; return;
} }
@ -110,35 +85,10 @@ void UserInputCtrl::OnJoy(wxJoyEvent& event) {
inputs_.clear(); inputs_.clear();
} }
inputs_.emplace(event); inputs_.insert(event.input());
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);
UpdateText(); UpdateText();
Navigate(); Navigate();
is_navigating_away_ = true;
} }
void UserInputCtrl::UpdateText() { void UserInputCtrl::UpdateText() {
@ -153,6 +103,7 @@ void UserInputCtrl::UpdateText() {
if (value.empty()) { if (value.empty()) {
SetValue(wxEmptyString); SetValue(wxEmptyString);
} else { } else {
// Remove the trailing comma.
SetValue(value.substr(0, value.size() - 1)); SetValue(value.substr(0, value.size() - 1));
} }
} }

View File

@ -3,6 +3,7 @@
#include <set> #include <set>
#include <wx/longlong.h>
#include <wx/string.h> #include <wx/string.h>
#include <wx/textctrl.h> #include <wx/textctrl.h>
#include <wx/validate.h> #include <wx/validate.h>
@ -10,7 +11,7 @@
#include "wx/config/game-control.h" #include "wx/config/game-control.h"
#include "wx/config/user-input.h" #include "wx/config/user-input.h"
#include "wx/widgets/sdljoy.h" #include "wx/widgets/user-input-event.h"
namespace widgets { namespace widgets {
@ -59,20 +60,23 @@ public:
void Clear() override; void Clear() override;
wxDECLARE_DYNAMIC_CLASS(UserInputCtrl); wxDECLARE_DYNAMIC_CLASS(UserInputCtrl);
wxDECLARE_EVENT_TABLE();
private: private:
// Event handlers. // Event handler.
void OnJoy(wxJoyEvent& event); void OnUserInputUp(widgets::UserInputEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnKeyUp(wxKeyEvent& event);
// Updates the text in the control to reflect the current inputs. // Updates the text in the control to reflect the current inputs.
void UpdateText(); void UpdateText();
bool is_multikey_ = false; 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<config::UserInput> inputs_; std::set<config::UserInput> inputs_;
}; };

View File

@ -0,0 +1,283 @@
#include "wx/widgets/user-input-event.h"
#include <vector>
#include <wx/event.h>
#include <wx/timer.h>
#include <wx/window.h>
#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<wxKeyCode>(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<wxKeyCode>(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<wxKeyModifier> 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<wxKeyModifier> 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<wxKeyModifier>& mods) {
int mod = wxMOD_NONE;
for (const wxKeyModifier m : mods) {
mod |= m;
}
return static_cast<wxKeyModifier>(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<wxKeyModifier> 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<config::UserInput> 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<wxKeyModifier> 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<config::UserInput> 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);

View File

@ -0,0 +1,67 @@
#ifndef WX_WIDGETS_USER_INPUT_EVENT_H_
#define WX_WIDGETS_USER_INPUT_EVENT_H_
#include <unordered_set>
#include <wx/clntdata.h>
#include <wx/event.h>
#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<wxKeyCode> active_keys_;
std::unordered_set<wxKeyModifier> active_mods_;
std::set<config::UserInput> 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_

View File

@ -57,8 +57,6 @@ using std::int32_t;
// GetAccel is inefficent anyway (often I don't want to convert to wxAccEnt) // GetAccel is inefficent anyway (often I don't want to convert to wxAccEnt)
// This is a working replacement for SetAccel, at least. // This is a working replacement for SetAccel, at least.
#include "wx/wxutil.h"
// wxrc helpers (for dynamic strings instead of constant) // wxrc helpers (for dynamic strings instead of constant)
#define XRCID_D(str) wxXmlResource::GetXRCID(str) #define XRCID_D(str) wxXmlResource::GetXRCID(str)
//#define XRCCTRL_D(win, id, type) (wxStaticCast((win).FindWindow(XRCID_D(id)), type)) //#define XRCCTRL_D(win, id, type) (wxStaticCast((win).FindWindow(XRCID_D(id)), type))

View File

@ -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();
}
}

View File

@ -1,8 +0,0 @@
#ifndef VBAM_WX_UTIL_H_
#define VBAM_WX_UTIL_H_
#include <wx/event.h>
int getKeyboardKeyCode(const wxKeyEvent& event);
#endif // VBAM_WX_UTIL_H_

View File

@ -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" #include "wx/wxvbam.h"
#ifdef __WXMSW__ #ifdef __WXMSW__
#include <windows.h> #include <windows.h>
#endif #endif
#ifdef __WXGTK__
#include <gdk/gdk.h>
#endif
#include <stdio.h> #include <stdio.h>
#include <wx/cmdline.h> #include <wx/cmdline.h>
#include <wx/display.h> #include <wx/display.h>
@ -31,18 +30,9 @@
#include <wx/zipstrm.h> #include <wx/zipstrm.h>
#include "components/user_config/user_config.h" #include "components/user_config/user_config.h"
#include "core/gb/gbGlobals.h"
#include "core/gba/gbaSound.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-over.h"
#include "wx/builtin-xrc.h"
#include "wx/config/game-control.h" #include "wx/config/game-control.h"
#include "wx/config/option-proxy.h" #include "wx/config/option-proxy.h"
#include "wx/config/option.h" #include "wx/config/option.h"
@ -53,9 +43,9 @@
#include "wx/widgets/user-input-ctrl.h" #include "wx/widgets/user-input-ctrl.h"
#include "wx/widgets/utils.h" #include "wx/widgets/utils.h"
#ifdef __WXGTK__ #if defined(VBAM_ENABLE_DEBUGGER)
#include <gdk/gdk.h> #include "core/gba/gbaRemote.h"
#endif #endif // defined(VBAM_ENABLE_DEBUGGER)
namespace { 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; 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() const wxString wxvbamApp::GetPluginsDir()
{ {
return wxStandardPaths::Get().GetPluginsDir(); return wxStandardPaths::Get().GetPluginsDir();
@ -836,6 +833,30 @@ wxvbamApp::~wxvbamApp() {
#endif #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() MainFrame::MainFrame()
: wxFrame(), : wxFrame(),
paused(false), paused(false),
@ -849,8 +870,6 @@ MainFrame::MainFrame()
keep_on_top_styler_(this), keep_on_top_styler_(this),
status_bar_observer_(config::OptionID::kGenStatusBar, status_bar_observer_(config::OptionID::kGenStatusBar,
std::bind(&MainFrame::OnStatusBarChanged, this)) { std::bind(&MainFrame::OnStatusBarChanged, this)) {
jpoll = new JoystickPoller();
this->Connect(wxID_ANY, wxEVT_SHOW, wxShowEventHandler(JoystickPoller::ShowDialog), jpoll, jpoll);
} }
MainFrame::~MainFrame() { MainFrame::~MainFrame() {
@ -983,19 +1002,15 @@ int MainFrame::FilterEvent(wxEvent& event) {
return wxEventFilter::Event_Skip; return wxEventFilter::Event_Skip;
} }
int command = 0; if (event.GetEventType() != VBAM_EVT_USER_INPUT_DOWN) {
if (event.GetEventType() == wxEVT_KEY_DOWN) { // We only treat "VBAM_EVT_USER_INPUT_DOWN" events here.
const wxKeyEvent& key_event = static_cast<wxKeyEvent&>(event); return wxEventFilter::Event_Skip;
command = gopts.shortcuts.CommandForInput(config::UserInput(key_event));
} else if (event.GetEventType() == wxEVT_JOY) {
const wxJoyEvent& joy_event = static_cast<wxJoyEvent&>(event);
if (joy_event.pressed()) {
// We ignore "button up" events here.
command = gopts.shortcuts.CommandForInput(config::UserInput(joy_event));
}
} }
const widgets::UserInputEvent& user_input_event = static_cast<widgets::UserInputEvent&>(event);
const int command = gopts.shortcuts.CommandForInput(user_input_event.input());
if (command == 0) { if (command == 0) {
// No associated command found.
return wxEventFilter::Event_Skip; return wxEventFilter::Event_Skip;
} }
@ -1028,58 +1043,6 @@ wxString MainFrame::GetGamePath(wxString path)
return game_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<wxJoystick> 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() void MainFrame::enable_menus()
{ {
for (int i = 0; i < ncmds; i++) for (int i = 0; i < ncmds; i++)
@ -1403,7 +1366,8 @@ void MainFrame::IdentifyRom()
// grabbing those keys, but I can't track it down. // grabbing those keys, but I can't track it down.
int wxvbamApp::FilterEvent(wxEvent& event) int wxvbamApp::FilterEvent(wxEvent& event)
{ {
if (frame) if (frame) {
return frame->FilterEvent(event); return frame->FilterEvent(event);
}
return wxApp::FilterEvent(event); return wxApp::FilterEvent(event);
} }

View File

@ -17,7 +17,8 @@
#include "wx/dialogs/base-dialog.h" #include "wx/dialogs/base-dialog.h"
#include "wx/widgets/dpi-support.h" #include "wx/widgets/dpi-support.h"
#include "wx/widgets/keep-on-top-styler.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/widgets/wxmisc.h"
#include "wx/wxhead.h" #include "wx/wxhead.h"
@ -30,7 +31,6 @@
#endif #endif
#include "wx/wxlogdebug.h" #include "wx/wxlogdebug.h"
#include "wx/wxutil.h"
template <typename T> template <typename T>
void CheckPointer(T pointer) void CheckPointer(T pointer)
@ -59,13 +59,7 @@ class MainFrame;
class wxvbamApp : public wxApp { class wxvbamApp : public wxApp {
public: public:
wxvbamApp() wxvbamApp();
: wxApp()
, pending_fullscreen(false)
, frame(NULL)
, using_wayland(false)
{
}
virtual bool OnInit(); virtual bool OnInit();
virtual int OnRun(); virtual int OnRun();
virtual bool OnCmdLineHelp(wxCmdLineParser&); virtual bool OnCmdLineHelp(wxCmdLineParser&);
@ -94,6 +88,8 @@ public:
// without this, global accels don't always work // without this, global accels don't always work
int FilterEvent(wxEvent&); int FilterEvent(wxEvent&);
widgets::SdlPoller* sdl_poller() { return &sdl_poller_; }
// vba-over.ini // vba-over.ini
wxFileConfig* overrides = nullptr; wxFileConfig* overrides = nullptr;
@ -130,9 +126,14 @@ protected:
int console_status = 0; int console_status = 0;
private: private:
// Returns the currently active event handler to use for user input events.
wxEvtHandler* GetJoyEventHandler();
wxPathList config_path; wxPathList config_path;
char* home = nullptr; char* home = nullptr;
widgets::SdlPoller sdl_poller_;
// Main configuration file. // Main configuration file.
wxFileName config_file_; wxFileName config_file_;
}; };
@ -216,8 +217,6 @@ public:
void GetMenuOptionBool(const wxString& menuName, bool* field); void GetMenuOptionBool(const wxString& menuName, bool* field);
void SetMenuOption(const wxString& menuName, bool value); void SetMenuOption(const wxString& menuName, bool value);
void SetJoystick();
int FilterEvent(wxEvent& event); int FilterEvent(wxEvent& event);
GameArea* GetPanel() GameArea* GetPanel()
@ -308,24 +307,11 @@ public:
virtual bool DialogOpened() { return dialog_opened != 0; } virtual bool DialogOpened() { return dialog_opened != 0; }
virtual void SetJoystickRumble(bool b) { joy.SetRumble(b); }
bool IsPaused(bool incendental = false) bool IsPaused(bool incendental = false)
{ {
return (paused && !pause_next && !incendental) || dialog_opened; 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 // required for building from xrc
DECLARE_DYNAMIC_CLASS(MainFrame); DECLARE_DYNAMIC_CLASS(MainFrame);
// required for event handling // required for event handling
@ -351,9 +337,6 @@ private:
checkable_mi_array_t checkable_mi; checkable_mi_array_t checkable_mi;
// recent menu item accels // recent menu item accels
wxMenu* recent; wxMenu* recent;
// joystick reader
wxJoyPoller joy;
JoystickPoller* jpoll = nullptr;
// quicker & more accurate than FindFocus() != NULL // quicker & more accurate than FindFocus() != NULL
bool focused; bool focused;
// One-time toggle to indicate that this object is fully initialized. This // One-time toggle to indicate that this object is fully initialized. This
@ -390,20 +373,6 @@ private:
#include "wx/cmdhandlers.h" #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() // a helper class to avoid forgetting StopModal()
class ModalPause { class ModalPause {
public: public:
@ -581,9 +550,8 @@ protected:
bool paused; bool paused;
void OnIdle(wxIdleEvent&); void OnIdle(wxIdleEvent&);
void OnKeyDown(wxKeyEvent& ev); void OnUserInputDown(widgets::UserInputEvent& event);
void OnKeyUp(wxKeyEvent& ev); void OnUserInputUp(widgets::UserInputEvent& event);
void OnSDLJoy(wxJoyEvent& ev);
void PaintEv(wxPaintEvent& ev); void PaintEv(wxPaintEvent& ev);
void EraseBackground(wxEraseEvent& ev); void EraseBackground(wxEraseEvent& ev);
void OnSize(wxSizeEvent& ev); void OnSize(wxSizeEvent& ev);