summaryrefslogtreecommitdiffstats
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp290
-rw-r--r--src/input_common/gcadapter/gc_adapter.h51
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp40
-rw-r--r--src/input_common/main.cpp263
-rw-r--r--src/input_common/main.h152
-rw-r--r--src/input_common/motion_emu.cpp17
-rw-r--r--src/input_common/motion_from_button.cpp34
-rw-r--r--src/input_common/motion_from_button.h25
-rw-r--r--src/input_common/motion_input.cpp305
-rw-r--r--src/input_common/motion_input.h73
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/input_common/sdl/sdl_impl.cpp665
-rw-r--r--src/input_common/sdl/sdl_impl.h10
-rw-r--r--src/input_common/settings.cpp40
-rw-r--r--src/input_common/settings.h352
-rw-r--r--src/input_common/touch_from_button.cpp50
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp185
-rw-r--r--src/input_common/udp/client.h77
-rw-r--r--src/input_common/udp/udp.cpp177
-rw-r--r--src/input_common/udp/udp.h54
22 files changed, 2380 insertions, 530 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 317c25bad..c84685214 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,14 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
+ motion_from_button.cpp
+ motion_from_button.h
+ motion_input.cpp
+ motion_input.h
+ settings.cpp
+ settings.h
+ touch_from_button.cpp
+ touch_from_button.h
gcadapter/gc_adapter.cpp
gcadapter/gc_adapter.h
gcadapter/gc_poller.cpp
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index 898a278a9..89c148aba 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -4,9 +4,20 @@
#include <chrono>
#include <thread>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
+#endif
#include <libusb.h>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
#include "common/logging/log.h"
+#include "common/param_package.h"
#include "input_common/gcadapter/gc_adapter.h"
+#include "input_common/settings.h"
namespace GCAdapter {
@@ -24,12 +35,9 @@ Adapter::Adapter() {
}
LOG_INFO(Input, "GC Adapter Initialization started");
- current_status = NO_ADAPTER_DETECTED;
- get_origin.fill(true);
-
const int init_res = libusb_init(&libusb_ctx);
if (init_res == LIBUSB_SUCCESS) {
- StartScanThread();
+ Setup();
} else {
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
}
@@ -37,9 +45,9 @@ Adapter::Adapter() {
GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) {
GCPadStatus pad = {};
+ const std::size_t offset = 1 + (9 * port);
- ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4);
- adapter_controllers_status[port] = type;
+ adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
static constexpr std::array<PadButton, 8> b1_buttons{
PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
@@ -54,14 +62,19 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad
PadButton::PAD_TRIGGER_L,
};
+ static constexpr std::array<PadAxes, 6> axes{
+ PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
+ PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
+ };
+
if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) {
// Controller may have been disconnected, recalibrate if reconnected.
get_origin[port] = true;
}
if (adapter_controllers_status[port] != ControllerTypes::None) {
- const u8 b1 = adapter_payload[1 + (9 * port) + 1];
- const u8 b2 = adapter_payload[1 + (9 * port) + 2];
+ const u8 b1 = adapter_payload[offset + 1];
+ const u8 b2 = adapter_payload[offset + 2];
for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
if ((b1 & (1U << i)) != 0) {
@@ -74,21 +87,13 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad
pad.button |= static_cast<u16>(b2_buttons[j]);
}
}
-
- pad.stick_x = adapter_payload[1 + (9 * port) + 3];
- pad.stick_y = adapter_payload[1 + (9 * port) + 4];
- pad.substick_x = adapter_payload[1 + (9 * port) + 5];
- pad.substick_y = adapter_payload[1 + (9 * port) + 6];
- pad.trigger_left = adapter_payload[1 + (9 * port) + 7];
- pad.trigger_right = adapter_payload[1 + (9 * port) + 8];
+ for (PadAxes axis : axes) {
+ const std::size_t index = static_cast<std::size_t>(axis);
+ pad.axis_values[index] = adapter_payload[offset + 3 + index];
+ }
if (get_origin[port]) {
- origin_status[port].stick_x = pad.stick_x;
- origin_status[port].stick_y = pad.stick_y;
- origin_status[port].substick_x = pad.substick_x;
- origin_status[port].substick_y = pad.substick_y;
- origin_status[port].trigger_left = pad.trigger_left;
- origin_status[port].trigger_right = pad.trigger_right;
+ origin_status[port].axis_values = pad.axis_values;
get_origin[port] = false;
}
}
@@ -101,82 +106,47 @@ void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
state.buttons.insert_or_assign(button_value, pad.button & button_value);
}
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x);
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y);
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x);
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y);
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left);
- state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right);
+ for (size_t i = 0; i < pad.axis_values.size(); ++i) {
+ state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]);
+ }
}
void Adapter::Read() {
LOG_DEBUG(Input, "GC Adapter Read() thread started");
- int payload_size_in, payload_size_copy;
+ int payload_size;
std::array<u8, 37> adapter_payload;
- std::array<u8, 37> adapter_payload_copy;
std::array<GCPadStatus, 4> pads;
while (adapter_thread_running) {
libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
- sizeof(adapter_payload), &payload_size_in, 16);
- payload_size_copy = 0;
- // this mutex might be redundant?
- {
- std::lock_guard<std::mutex> lk(s_mutex);
- std::copy(std::begin(adapter_payload), std::end(adapter_payload),
- std::begin(adapter_payload_copy));
- payload_size_copy = payload_size_in;
- }
+ sizeof(adapter_payload), &payload_size, 16);
- if (payload_size_copy != sizeof(adapter_payload_copy) ||
- adapter_payload_copy[0] != LIBUSB_DT_HID) {
- LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy,
- adapter_payload_copy[0]);
+ if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) {
+ LOG_ERROR(Input,
+ "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?",
+ payload_size, adapter_payload[0]);
adapter_thread_running = false; // error reading from adapter, stop reading.
break;
}
for (std::size_t port = 0; port < pads.size(); ++port) {
- pads[port] = GetPadStatus(port, adapter_payload_copy);
+ pads[port] = GetPadStatus(port, adapter_payload);
if (DeviceConnected(port) && configuring) {
if (pads[port].button != 0) {
pad_queue[port].Push(pads[port]);
}
- // Accounting for a threshold here because of some controller variance
- if (pads[port].stick_x > origin_status[port].stick_x + pads[port].THRESHOLD ||
- pads[port].stick_x < origin_status[port].stick_x - pads[port].THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::StickX;
- pads[port].axis_value = pads[port].stick_x;
- pad_queue[port].Push(pads[port]);
- }
- if (pads[port].stick_y > origin_status[port].stick_y + pads[port].THRESHOLD ||
- pads[port].stick_y < origin_status[port].stick_y - pads[port].THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::StickY;
- pads[port].axis_value = pads[port].stick_y;
- pad_queue[port].Push(pads[port]);
- }
- if (pads[port].substick_x > origin_status[port].substick_x + pads[port].THRESHOLD ||
- pads[port].substick_x < origin_status[port].substick_x - pads[port].THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::SubstickX;
- pads[port].axis_value = pads[port].substick_x;
- pad_queue[port].Push(pads[port]);
- }
- if (pads[port].substick_y > origin_status[port].substick_y + pads[port].THRESHOLD ||
- pads[port].substick_y < origin_status[port].substick_y - pads[port].THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::SubstickY;
- pads[port].axis_value = pads[port].substick_y;
- pad_queue[port].Push(pads[port]);
- }
- if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::TriggerLeft;
- pads[port].axis_value = pads[port].trigger_left;
- pad_queue[port].Push(pads[port]);
- }
- if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) {
- pads[port].axis = GCAdapter::PadAxes::TriggerRight;
- pads[port].axis_value = pads[port].trigger_right;
- pad_queue[port].Push(pads[port]);
+ // Accounting for a threshold here to ensure an intentional press
+ for (size_t i = 0; i < pads[port].axis_values.size(); ++i) {
+ const u8 value = pads[port].axis_values[i];
+ const u8 origin = origin_status[port].axis_values[i];
+
+ if (value > origin + pads[port].THRESHOLD ||
+ value < origin - pads[port].THRESHOLD) {
+ pads[port].axis = static_cast<PadAxes>(i);
+ pads[port].axis_value = pads[port].axis_values[i];
+ pad_queue[port].Push(pads[port]);
+ }
}
}
PadToState(pads[port], state[port]);
@@ -185,42 +155,11 @@ void Adapter::Read() {
}
}
-void Adapter::ScanThreadFunc() {
- LOG_INFO(Input, "GC Adapter scanning thread started");
-
- while (detect_thread_running) {
- if (usb_adapter_handle == nullptr) {
- std::lock_guard<std::mutex> lk(initialization_mutex);
- Setup();
- }
- std::this_thread::sleep_for(std::chrono::milliseconds(500));
- }
-}
-
-void Adapter::StartScanThread() {
- if (detect_thread_running) {
- return;
- }
- if (!libusb_ctx) {
- return;
- }
-
- detect_thread_running = true;
- detect_thread = std::thread(&Adapter::ScanThreadFunc, this);
-}
-
-void Adapter::StopScanThread() {
- detect_thread_running = false;
- detect_thread.join();
-}
-
void Adapter::Setup() {
- // Reset the error status in case the adapter gets unplugged
- if (current_status < 0) {
- current_status = NO_ADAPTER_DETECTED;
- }
-
+ // Initialize all controllers as unplugged
adapter_controllers_status.fill(ControllerTypes::None);
+ // Initialize all ports to store axis origin values
+ get_origin.fill(true);
// pointer to list of connected usb devices
libusb_device** devices{};
@@ -229,8 +168,6 @@ void Adapter::Setup() {
const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
if (device_count < 0) {
LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
- detect_thread_running = false; // Stop the loop constantly checking for gc adapter
- // TODO: For hotplug+gc adapter checkbox implementation, revert this.
return;
}
@@ -244,9 +181,6 @@ void Adapter::Setup() {
}
libusb_free_device_list(devices, 1);
}
- // Break out of the ScanThreadFunc() loop that is constantly looking for the device
- // Assumes user has GC adapter plugged in before launch to use the adapter
- detect_thread_running = false;
}
bool Adapter::CheckDeviceAccess(libusb_device* device) {
@@ -331,32 +265,23 @@ void Adapter::GetGCEndpoint(libusb_device* device) {
sizeof(clear_payload), nullptr, 16);
adapter_thread_running = true;
- current_status = ADAPTER_DETECTED;
- adapter_input_thread = std::thread([=] { Read(); }); // Read input
+ adapter_input_thread = std::thread(&Adapter::Read, this);
}
Adapter::~Adapter() {
- StopScanThread();
Reset();
}
void Adapter::Reset() {
- std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock);
- if (!lock.try_lock()) {
- return;
- }
- if (current_status != ADAPTER_DETECTED) {
- return;
- }
-
if (adapter_thread_running) {
adapter_thread_running = false;
}
- adapter_input_thread.join();
+ if (adapter_input_thread.joinable()) {
+ adapter_input_thread.join();
+ }
adapter_controllers_status.fill(ControllerTypes::None);
get_origin.fill(true);
- current_status = NO_ADAPTER_DETECTED;
if (usb_adapter_handle) {
libusb_release_interface(usb_adapter_handle, 1);
@@ -369,7 +294,93 @@ void Adapter::Reset() {
}
}
-bool Adapter::DeviceConnected(std::size_t port) {
+std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t port = 0; port < state.size(); ++port) {
+ if (!DeviceConnected(port)) {
+ continue;
+ }
+ std::string name = fmt::format("Gamecube Controller {}", port);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "gcpad"},
+ {"display", std::move(name)},
+ {"port", std::to_string(port)},
+ });
+ }
+ return devices;
+}
+
+InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ // This list is missing ZL/ZR since those are not considered buttons.
+ // We will add those afterwards
+ // This list also excludes any button that can't be really mapped
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
+ switch_to_gcadapter_button = {
+ std::pair{Settings::NativeButton::A, PadButton::PAD_BUTTON_A},
+ {Settings::NativeButton::B, PadButton::PAD_BUTTON_B},
+ {Settings::NativeButton::X, PadButton::PAD_BUTTON_X},
+ {Settings::NativeButton::Y, PadButton::PAD_BUTTON_Y},
+ {Settings::NativeButton::Plus, PadButton::PAD_BUTTON_START},
+ {Settings::NativeButton::DLeft, PadButton::PAD_BUTTON_LEFT},
+ {Settings::NativeButton::DUp, PadButton::PAD_BUTTON_UP},
+ {Settings::NativeButton::DRight, PadButton::PAD_BUTTON_RIGHT},
+ {Settings::NativeButton::DDown, PadButton::PAD_BUTTON_DOWN},
+ {Settings::NativeButton::SL, PadButton::PAD_TRIGGER_L},
+ {Settings::NativeButton::SR, PadButton::PAD_TRIGGER_R},
+ {Settings::NativeButton::R, PadButton::PAD_TRIGGER_Z},
+ };
+ if (!params.Has("port")) {
+ return {};
+ }
+
+ InputCommon::ButtonMapping mapping{};
+ for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<int>(gcadapter_button));
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+
+ // Add the missing bindings for ZL/ZR
+ static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
+ switch_to_gcadapter_axis = {
+ std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
+ {Settings::NativeButton::ZR, PadAxes::TriggerRight},
+ };
+ for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
+ Common::ParamPackage button_params({{"engine", "gcpad"}});
+ button_params.Set("port", params.Get("port", 0));
+ button_params.Set("button", static_cast<int>(PadButton::PAD_STICK));
+ button_params.Set("axis", static_cast<int>(gcadapter_axis));
+ mapping.insert_or_assign(switch_button, std::move(button_params));
+ }
+ return mapping;
+}
+
+InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("port")) {
+ return {};
+ }
+
+ InputCommon::AnalogMapping mapping = {};
+ Common::ParamPackage left_analog_params;
+ left_analog_params.Set("engine", "gcpad");
+ left_analog_params.Set("port", params.Get("port", 0));
+ left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
+ left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
+ Common::ParamPackage right_analog_params;
+ right_analog_params.Set("engine", "gcpad");
+ right_analog_params.Set("port", params.Get("port", 0));
+ right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
+ right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
+ return mapping;
+}
+
+bool Adapter::DeviceConnected(std::size_t port) const {
return adapter_controllers_status[port] != ControllerTypes::None;
}
@@ -409,24 +420,7 @@ const std::array<GCState, 4>& Adapter::GetPadState() const {
}
int Adapter::GetOriginValue(int port, int axis) const {
- const auto& status = origin_status[port];
-
- switch (static_cast<PadAxes>(axis)) {
- case PadAxes::StickX:
- return status.stick_x;
- case PadAxes::StickY:
- return status.stick_y;
- case PadAxes::SubstickX:
- return status.substick_x;
- case PadAxes::SubstickY:
- return status.substick_y;
- case PadAxes::TriggerLeft:
- return status.trigger_left;
- case PadAxes::TriggerRight:
- return status.trigger_right;
- default:
- return 0;
- }
+ return origin_status[port].axis_values[axis];
}
} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
index 3586c8bda..75bf9fe74 100644
--- a/src/input_common/gcadapter/gc_adapter.h
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -10,6 +10,7 @@
#include <unordered_map>
#include "common/common_types.h"
#include "common/threadsafe_queue.h"
+#include "input_common/main.h"
struct libusb_context;
struct libusb_device;
@@ -47,24 +48,10 @@ enum class PadAxes : u8 {
};
struct GCPadStatus {
- u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
- u8 stick_x{}; // 0 <= stick_x <= 255
- u8 stick_y{}; // 0 <= stick_y <= 255
- u8 substick_x{}; // 0 <= substick_x <= 255
- u8 substick_y{}; // 0 <= substick_y <= 255
- u8 trigger_left{}; // 0 <= trigger_left <= 255
- u8 trigger_right{}; // 0 <= trigger_right <= 255
-
- static constexpr u8 MAIN_STICK_CENTER_X = 0x80;
- static constexpr u8 MAIN_STICK_CENTER_Y = 0x80;
- static constexpr u8 MAIN_STICK_RADIUS = 0x7f;
- static constexpr u8 C_STICK_CENTER_X = 0x80;
- static constexpr u8 C_STICK_CENTER_Y = 0x80;
- static constexpr u8 C_STICK_RADIUS = 0x7f;
- static constexpr u8 THRESHOLD = 10;
-
- // 256/4, at least a quarter press to count as a press. For polling mostly
- static constexpr u8 TRIGGER_THRESHOLD = 64;
+ u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
+
+ std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes
+ static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling
u8 port{};
PadAxes axis{PadAxes::Undefined};
@@ -78,11 +65,6 @@ struct GCState {
enum class ControllerTypes { None, Wired, Wireless };
-enum {
- NO_ADAPTER_DETECTED = 0,
- ADAPTER_DETECTED = 1,
-};
-
class Adapter {
public:
/// Initialize the GC Adapter capture and read sequence
@@ -94,8 +76,12 @@ public:
void BeginConfiguration();
void EndConfiguration();
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+ InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
+ InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
+
/// Returns true if there is a device connected to port
- bool DeviceConnected(std::size_t port);
+ bool DeviceConnected(std::size_t port) const;
std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
@@ -111,12 +97,6 @@ private:
void PadToState(const GCPadStatus& pad, GCState& state);
void Read();
- void ScanThreadFunc();
- /// Begin scanning for the GC Adapter.
- void StartScanThread();
-
- /// Stop scanning for the adapter
- void StopScanThread();
/// Resets status of device connected to port
void ResetDeviceType(std::size_t port);
@@ -133,19 +113,11 @@ private:
/// For use in initialization, querying devices to find the adapter
void Setup();
- int current_status = NO_ADAPTER_DETECTED;
libusb_device_handle* usb_adapter_handle = nullptr;
- std::array<ControllerTypes, 4> adapter_controllers_status{};
-
- std::mutex s_mutex;
std::thread adapter_input_thread;
bool adapter_thread_running;
- std::mutex initialization_mutex;
- std::thread detect_thread;
- bool detect_thread_running = false;
-
libusb_context* libusb_ctx;
u8 input_endpoint = 0;
@@ -153,10 +125,11 @@ private:
bool configuring = false;
- std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
std::array<GCState, 4> state;
std::array<bool, 4> get_origin;
std::array<GCPadStatus, 4> origin_status;
+ std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
+ std::array<ControllerTypes, 4> adapter_controllers_status{};
};
} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index 96e22d3ad..92e9e8e89 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -15,7 +15,7 @@ namespace InputCommon {
class GCButton final : public Input::ButtonDevice {
public:
- explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
+ explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
@@ -30,15 +30,16 @@ public:
private:
const int port;
const int button;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
public:
explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
- GCAdapter::Adapter* adapter)
+ const GCAdapter::Adapter* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
- gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {}
+ gcadapter(adapter),
+ origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
@@ -59,7 +60,7 @@ private:
const int axis;
float threshold;
bool trigger_if_greater;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
const float origin_value;
};
@@ -76,8 +77,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param
// button is not an axis/stick button
if (button_id != PAD_STICK_ID) {
- auto button = std::make_unique<GCButton>(port, button_id, adapter.get());
- return std::move(button);
+ return std::make_unique<GCButton>(port, button_id, adapter.get());
}
// For Axis buttons, used by the binary sticks.
@@ -149,19 +149,18 @@ void GCButtonFactory::EndConfiguration() {
class GCAnalog final : public Input::AnalogDevice {
public:
- GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter)
+ GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_,
+ const GCAdapter::Adapter* adapter, float range_)
: port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
- origin_value_x(adapter->GetOriginValue(port_, axis_x_)),
- origin_value_y(adapter->GetOriginValue(port_, axis_y_)) {}
+ origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
+ origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
+ range(range_) {}
float GetAxis(int axis) const {
if (gcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y;
- // division is not by a perfect 128 to account for some variance in center location
- // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
- // [20-230]
- return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / 95.0f;
+ return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);
}
return 0.0f;
}
@@ -194,7 +193,7 @@ public:
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
- const float directional_deadzone = 0.4f;
+ const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
@@ -213,9 +212,10 @@ private:
const int axis_x;
const int axis_y;
const float deadzone;
- GCAdapter::Adapter* gcadapter;
+ const GCAdapter::Adapter* gcadapter;
const float origin_value_x;
const float origin_value_y;
+ const float range;
mutable std::mutex mutex;
};
@@ -234,9 +234,10 @@ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::Param
const int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
- return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get());
+ return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
}
void GCAnalogFactory::BeginConfiguration() {
@@ -264,7 +265,8 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<int>(port);
- } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) {
+ } else if (analog_y_axis == -1 && analog_x_axis != axis &&
+ controller_number == static_cast<int>(port)) {
analog_y_axis = axis;
}
}
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index b9d5d0ec3..3d97d95f7 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,9 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
+#include "input_common/motion_from_button.h"
+#include "input_common/touch_from_button.h"
+#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
#ifdef HAVE_SDL2
#include "input_common/sdl/sdl.h"
@@ -18,67 +21,227 @@
namespace InputCommon {
-static std::shared_ptr<Keyboard> keyboard;
-static std::shared_ptr<MotionEmu> motion_emu;
+struct InputSubsystem::Impl {
+ void Initialize() {
+ gcadapter = std::make_shared<GCAdapter::Adapter>();
+ gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
+ Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
+ gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
+ Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+
+ keyboard = std::make_shared<Keyboard>();
+ Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
+ Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
+ std::make_shared<AnalogFromButton>());
+ Input::RegisterFactory<Input::MotionDevice>("keyboard",
+ std::make_shared<MotionFromButton>());
+ motion_emu = std::make_shared<MotionEmu>();
+ Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
+ std::make_shared<TouchFromButtonFactory>());
+
#ifdef HAVE_SDL2
-static std::unique_ptr<SDL::State> sdl;
+ sdl = SDL::Init();
#endif
-static std::unique_ptr<CemuhookUDP::State> udp;
-static std::shared_ptr<GCButtonFactory> gcbuttons;
-static std::shared_ptr<GCAnalogFactory> gcanalog;
-
-void Init() {
- auto gcadapter = std::make_shared<GCAdapter::Adapter>();
- gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
- Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
- gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
- Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
-
- keyboard = std::make_shared<Keyboard>();
- Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
- Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
- std::make_shared<AnalogFromButton>());
- motion_emu = std::make_shared<MotionEmu>();
- Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+ udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
+ udpmotion = std::make_shared<UDPMotionFactory>(udp);
+ Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
+ udptouch = std::make_shared<UDPTouchFactory>(udp);
+ Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
+ }
+
+ void Shutdown() {
+ Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
+ Input::UnregisterFactory<Input::MotionDevice>("keyboard");
+ keyboard.reset();
+ Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
+ Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
+ motion_emu.reset();
+ Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
#ifdef HAVE_SDL2
- sdl = SDL::Init();
+ sdl.reset();
#endif
+ Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
+ Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
- udp = CemuhookUDP::Init();
-}
+ gcbuttons.reset();
+ gcanalog.reset();
+
+ Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+ Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
+
+ udpmotion.reset();
+ udptouch.reset();
+ }
+
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices = {
+ Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
+ Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
+ };
+#ifdef HAVE_SDL2
+ auto sdl_devices = sdl->GetInputDevices();
+ devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
+#endif
+ auto udp_devices = udp->GetInputDevices();
+ devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
+ auto gcpad_devices = gcadapter->GetInputDevices();
+ devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
+ return devices;
+ }
+
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "key") {
+ // TODO consider returning the SDL key codes for the default keybindings
+ return {};
+ }
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetAnalogMappingForDevice(params);
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetAnalogMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
+
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "key") {
+ // TODO consider returning the SDL key codes for the default keybindings
+ return {};
+ }
+ if (params.Get("class", "") == "gcpad") {
+ return gcadapter->GetButtonMappingForDevice(params);
+ }
+#ifdef HAVE_SDL2
+ if (params.Get("class", "") == "sdl") {
+ return sdl->GetButtonMappingForDevice(params);
+ }
+#endif
+ return {};
+ }
+
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(
+ const Common::ParamPackage& params) const {
+ if (!params.Has("class") || params.Get("class", "") == "any") {
+ return {};
+ }
+ if (params.Get("class", "") == "cemuhookudp") {
+ // TODO return the correct motion device
+ return {};
+ }
+ return {};
+ }
-void Shutdown() {
- Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
- keyboard.reset();
- Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
- Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
- motion_emu.reset();
+ std::shared_ptr<Keyboard> keyboard;
+ std::shared_ptr<MotionEmu> motion_emu;
#ifdef HAVE_SDL2
- sdl.reset();
+ std::unique_ptr<SDL::State> sdl;
#endif
- udp.reset();
- Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
- Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+ std::shared_ptr<GCButtonFactory> gcbuttons;
+ std::shared_ptr<GCAnalogFactory> gcanalog;
+ std::shared_ptr<UDPMotionFactory> udpmotion;
+ std::shared_ptr<UDPTouchFactory> udptouch;
+ std::shared_ptr<CemuhookUDP::Client> udp;
+ std::shared_ptr<GCAdapter::Adapter> gcadapter;
+};
+
+InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
+
+InputSubsystem::~InputSubsystem() = default;
+
+void InputSubsystem::Initialize() {
+ impl->Initialize();
+}
+
+void InputSubsystem::Shutdown() {
+ impl->Shutdown();
+}
+
+Keyboard* InputSubsystem::GetKeyboard() {
+ return impl->keyboard.get();
+}
+
+const Keyboard* InputSubsystem::GetKeyboard() const {
+ return impl->keyboard.get();
+}
+
+MotionEmu* InputSubsystem::GetMotionEmu() {
+ return impl->motion_emu.get();
+}
- gcbuttons.reset();
- gcanalog.reset();
+const MotionEmu* InputSubsystem::GetMotionEmu() const {
+ return impl->motion_emu.get();
}
-Keyboard* GetKeyboard() {
- return keyboard.get();
+std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
+ return impl->GetInputDevices();
}
-MotionEmu* GetMotionEmu() {
- return motion_emu.get();
+AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetAnalogMappingForDevice(device);
}
-GCButtonFactory* GetGCButtons() {
- return gcbuttons.get();
+ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
+ return impl->GetButtonMappingForDevice(device);
}
-GCAnalogFactory* GetGCAnalogs() {
- return gcanalog.get();
+GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
+ return impl->gcanalog.get();
+}
+
+const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
+ return impl->gcanalog.get();
+}
+
+GCButtonFactory* InputSubsystem::GetGCButtons() {
+ return impl->gcbuttons.get();
+}
+
+const GCButtonFactory* InputSubsystem::GetGCButtons() const {
+ return impl->gcbuttons.get();
+}
+
+UDPMotionFactory* InputSubsystem::GetUDPMotions() {
+ return impl->udpmotion.get();
+}
+
+const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
+ return impl->udpmotion.get();
+}
+
+UDPTouchFactory* InputSubsystem::GetUDPTouch() {
+ return impl->udptouch.get();
+}
+
+const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
+ return impl->udptouch.get();
+}
+
+void InputSubsystem::ReloadInputDevices() {
+ if (!impl->udp) {
+ return;
+ }
+ impl->udp->ReloadUDPClient();
+}
+
+std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
+ Polling::DeviceType type) const {
+#ifdef HAVE_SDL2
+ return impl->sdl->GetPollers(type);
+#else
+ return {};
+#endif
}
std::string GenerateKeyboardParam(int key_code) {
@@ -102,18 +265,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
};
return circle_pad_param.Serialize();
}
-
-namespace Polling {
-
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
- std::vector<std::unique_ptr<DevicePoller>> pollers;
-
-#ifdef HAVE_SDL2
- pollers = sdl->GetPollers(type);
-#endif
-
- return pollers;
-}
-
-} // namespace Polling
} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 0e32856f6..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -6,45 +6,29 @@
#include <memory>
#include <string>
+#include <unordered_map>
#include <vector>
-#include "input_common/gcadapter/gc_poller.h"
namespace Common {
class ParamPackage;
}
-namespace InputCommon {
-
-/// Initializes and registers all built-in input device factories.
-void Init();
-
-/// Deregisters all built-in input device factories and shuts them down.
-void Shutdown();
-
-class Keyboard;
-
-/// Gets the keyboard button device factory.
-Keyboard* GetKeyboard();
-
-class MotionEmu;
-
-/// Gets the motion emulation factory.
-MotionEmu* GetMotionEmu();
-
-GCButtonFactory* GetGCButtons();
-
-GCAnalogFactory* GetGCAnalogs();
+namespace Settings::NativeAnalog {
+enum Values : int;
+}
-/// Generates a serialized param package for creating a keyboard button device
-std::string GenerateKeyboardParam(int key_code);
+namespace Settings::NativeButton {
+enum Values : int;
+}
-/// Generates a serialized param package for creating an analog device taking input from keyboard
-std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
- int key_modifier, float modifier_scale);
+namespace Settings::NativeMotion {
+enum Values : int;
+}
+namespace InputCommon {
namespace Polling {
-enum class DeviceType { Button, Analog };
+enum class DeviceType { Button, AnalogPreferred, Motion };
/**
* A class that can be used to get inputs from an input device like controllers without having to
@@ -54,7 +38,9 @@ class DevicePoller {
public:
virtual ~DevicePoller() = default;
/// Setup and start polling for inputs, should be called before GetNextInput
- virtual void Start() = 0;
+ /// If a device_id is provided, events should be filtered to only include events from this
+ /// device id
+ virtual void Start(const std::string& device_id = "") = 0;
/// Stop polling
virtual void Stop() = 0;
/**
@@ -64,8 +50,110 @@ public:
*/
virtual Common::ParamPackage GetNextInput() = 0;
};
-
-// Get all DevicePoller from all backends for a specific device type
-std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
} // namespace Polling
+
+class GCAnalogFactory;
+class GCButtonFactory;
+class UDPMotionFactory;
+class UDPTouchFactory;
+class Keyboard;
+class MotionEmu;
+
+/**
+ * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
+ * mapping for the device. This is currently only implemented for the SDL backend devices.
+ */
+using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
+using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
+using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
+
+class InputSubsystem {
+public:
+ explicit InputSubsystem();
+ ~InputSubsystem();
+
+ InputSubsystem(const InputSubsystem&) = delete;
+ InputSubsystem& operator=(const InputSubsystem&) = delete;
+
+ InputSubsystem(InputSubsystem&&) = delete;
+ InputSubsystem& operator=(InputSubsystem&&) = delete;
+
+ /// Initializes and registers all built-in input device factories.
+ void Initialize();
+
+ /// Unregisters all built-in input device factories and shuts them down.
+ void Shutdown();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] Keyboard* GetKeyboard();
+
+ /// Retrieves the underlying keyboard device.
+ [[nodiscard]] const Keyboard* GetKeyboard() const;
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] MotionEmu* GetMotionEmu();
+
+ /// Retrieves the underlying motion emulation factory.
+ [[nodiscard]] const MotionEmu* GetMotionEmu() const;
+
+ /**
+ * Returns all available input devices that this Factory can create a new device with.
+ * Each returned ParamPackage should have a `display` field used for display, a class field for
+ * backends to determine if this backend is meant to service the request and any other
+ * information needed to identify this in the backend later.
+ */
+ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ /// Retrieves the analog mappings for the given device.
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the button mappings for the given device.
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the motion mappings for the given device.
+ [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
+
+ /// Retrieves the underlying GameCube analog handler.
+ [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] GCButtonFactory* GetGCButtons();
+
+ /// Retrieves the underlying GameCube button handler.
+ [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] UDPMotionFactory* GetUDPMotions();
+
+ /// Retrieves the underlying udp motion handler.
+ [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] UDPTouchFactory* GetUDPTouch();
+
+ /// Retrieves the underlying udp touch handler.
+ [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
+
+ /// Reloads the input devices
+ void ReloadInputDevices();
+
+ /// Get all DevicePoller from all backends for a specific device type
+ [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
+ Polling::DeviceType type) const;
+
+private:
+ struct Impl;
+ std::unique_ptr<Impl> impl;
+};
+
+/// Generates a serialized param package for creating a keyboard button device
+std::string GenerateKeyboardParam(int key_code);
+
+/// Generates a serialized param package for creating an analog device taking input from keyboard
+std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
+ int key_modifier, float modifier_scale);
+
} // namespace InputCommon
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index d4cdf76a3..69fd3c1d2 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -56,7 +56,7 @@ public:
is_tilting = false;
}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
+ Input::MotionStatus GetStatus() {
std::lock_guard guard{status_mutex};
return status;
}
@@ -76,7 +76,7 @@ private:
Common::Event shutdown_event;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
+ Input::MotionStatus status;
std::mutex status_mutex;
// Note: always keep the thread declaration at the end so that other objects are initialized
@@ -113,10 +113,19 @@ private:
gravity = QuaternionRotate(inv_q, gravity);
angular_rate = QuaternionRotate(inv_q, angular_rate);
+ // TODO: Calculate the correct rotation vector and orientation matrix
+ const auto matrix4x4 = q.ToMatrix();
+ const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
+ const std::array orientation{
+ Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
+ };
+
// Update the sensor state
{
std::lock_guard guard{status_mutex};
- status = std::make_tuple(gravity, angular_rate);
+ status = std::make_tuple(gravity, angular_rate, rotation, orientation);
}
}
}
@@ -131,7 +140,7 @@ public:
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
+ Input::MotionStatus GetStatus() const override {
return device->GetStatus();
}
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
new file mode 100644
index 000000000..9d459f963
--- /dev/null
+++ b/src/input_common/motion_from_button.cpp
@@ -0,0 +1,34 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/motion_from_button.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+class MotionKey final : public Input::MotionDevice {
+public:
+ using Button = std::unique_ptr<Input::ButtonDevice>;
+
+ MotionKey(Button key_) : key(std::move(key_)) {}
+
+ Input::MotionStatus GetStatus() const override {
+
+ if (key->GetStatus()) {
+ return motion.GetRandomMotion(2, 6);
+ }
+ return motion.GetRandomMotion(0, 0);
+ }
+
+private:
+ Button key;
+ InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
+};
+
+std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
+ auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
+ return std::make_unique<MotionKey>(std::move(key));
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h
new file mode 100644
index 000000000..a959046fb
--- /dev/null
+++ b/src/input_common/motion_from_button.h
@@ -0,0 +1,25 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * An motion device factory that takes a keyboard button and uses it as a random
+ * motion device.
+ */
+class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
+public:
+ /**
+ * Creates an motion device from button devices
+ * @param params contains parameters for creating the device:
+ * - "key": a serialized ParamPackage for creating a button device
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..e89019723
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,305 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <random>
+#include "common/math_util.h"
+#include "input_common/motion_input.h"
+
+namespace InputCommon {
+
+MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd)
+ : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {}
+
+void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
+ accel = acceleration;
+}
+
+void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
+ gyro = gyroscope - gyro_drift;
+
+ // Auto adjust drift to minimize drift
+ if (!IsMoving(0.1f)) {
+ gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
+ }
+
+ if (gyro.Length2() < gyro_threshold) {
+ gyro = {};
+ } else {
+ only_accelerometer = false;
+ }
+}
+
+void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
+ quat = quaternion;
+}
+
+void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
+ gyro_drift = drift;
+}
+
+void MotionInput::SetGyroThreshold(f32 threshold) {
+ gyro_threshold = threshold;
+}
+
+void MotionInput::EnableReset(bool reset) {
+ reset_enabled = reset;
+}
+
+void MotionInput::ResetRotations() {
+ rotations = {};
+}
+
+bool MotionInput::IsMoving(f32 sensitivity) const {
+ return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
+}
+
+bool MotionInput::IsCalibrated(f32 sensitivity) const {
+ return real_error.Length() < sensitivity;
+}
+
+void MotionInput::UpdateRotation(u64 elapsed_time) {
+ const f32 sample_period = elapsed_time / 1000000.0f;
+ if (sample_period > 0.1f) {
+ return;
+ }
+ rotations += gyro * sample_period;
+}
+
+void MotionInput::UpdateOrientation(u64 elapsed_time) {
+ if (!IsCalibrated(0.1f)) {
+ ResetOrientation();
+ }
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+ const f32 sample_period = elapsed_time / 1000000.0f;
+
+ // Ignore invalid elapsed time
+ if (sample_period > 0.1f) {
+ return;
+ }
+
+ const auto normal_accel = accel.Normalized();
+ auto rad_gyro = gyro * Common::PI * 2;
+ const f32 swap = rad_gyro.x;
+ rad_gyro.x = rad_gyro.y;
+ rad_gyro.y = -swap;
+ rad_gyro.z = -rad_gyro.z;
+
+ // Clear gyro values if there is no gyro present
+ if (only_accelerometer) {
+ rad_gyro.x = 0;
+ rad_gyro.y = 0;
+ rad_gyro.z = 0;
+ }
+
+ // Ignore drift correction if acceleration is not reliable
+ if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ // Prevent integral windup
+ if (ki != 0.0f && !IsCalibrated(0.05f)) {
+ integral_error += real_error;
+ } else {
+ integral_error = {};
+ }
+
+ // Apply feedback terms
+ if (!only_accelerometer) {
+ rad_gyro += kp * real_error;
+ rad_gyro += ki * integral_error;
+ rad_gyro += kd * derivative_error;
+ } else {
+ // Give more weight to acelerometer values to compensate for the lack of gyro
+ rad_gyro += 35.0f * kp * real_error;
+ rad_gyro += 10.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ // Emulate gyro values for games that need them
+ gyro.x = -rad_gyro.y;
+ gyro.y = rad_gyro.x;
+ gyro.z = -rad_gyro.z;
+ UpdateRotation(elapsed_time);
+ }
+ }
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+}
+
+std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
+ const Common::Quaternion<float> quad{
+ .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
+ .w = -quat.xyz[2],
+ };
+ const std::array<float, 16> matrix4x4 = quad.ToMatrix();
+
+ return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
+ Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
+ Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
+}
+
+Common::Vec3f MotionInput::GetAcceleration() const {
+ return accel;
+}
+
+Common::Vec3f MotionInput::GetGyroscope() const {
+ return gyro;
+}
+
+Common::Quaternion<f32> MotionInput::GetQuaternion() const {
+ return quat;
+}
+
+Common::Vec3f MotionInput::GetRotations() const {
+ return rotations;
+}
+
+Input::MotionStatus MotionInput::GetMotion() const {
+ const Common::Vec3f gyroscope = GetGyroscope();
+ const Common::Vec3f accelerometer = GetAcceleration();
+ const Common::Vec3f rotation = GetRotations();
+ const std::array<Common::Vec3f, 3> orientation = GetOrientation();
+ return {accelerometer, gyroscope, rotation, orientation};
+}
+
+Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<s16> distribution(-1000, 1000);
+ const Common::Vec3f gyroscope = {
+ distribution(gen) * 0.001f,
+ distribution(gen) * 0.001f,
+ distribution(gen) * 0.001f,
+ };
+ const Common::Vec3f accelerometer = {
+ distribution(gen) * 0.001f,
+ distribution(gen) * 0.001f,
+ distribution(gen) * 0.001f,
+ };
+ const Common::Vec3f rotation = {};
+ const std::array<Common::Vec3f, 3> orientation = {
+ Common::Vec3f{1.0f, 0, 0},
+ Common::Vec3f{0, 1.0f, 0},
+ Common::Vec3f{0, 0, 1.0f},
+ };
+ return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation};
+}
+
+void MotionInput::ResetOrientation() {
+ if (!reset_enabled || only_accelerometer) {
+ return;
+ }
+ if (!IsMoving(0.5f) && accel.z <= -0.9f) {
+ ++reset_counter;
+ if (reset_counter > 900) {
+ quat.w = 0;
+ quat.xyz[0] = 0;
+ quat.xyz[1] = 0;
+ quat.xyz[2] = -1;
+ SetOrientationFromAccelerometer();
+ integral_error = {};
+ reset_counter = 0;
+ }
+ } else {
+ reset_counter = 0;
+ }
+}
+
+void MotionInput::SetOrientationFromAccelerometer() {
+ int iterations = 0;
+ const f32 sample_period = 0.015f;
+
+ const auto normal_accel = accel.Normalized();
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ while (!IsCalibrated(0.01f) && ++iterations < 100) {
+ // Short name local variable for readability
+ f32 q1 = quat.w;
+ f32 q2 = quat.xyz[0];
+ f32 q3 = quat.xyz[1];
+ f32 q4 = quat.xyz[2];
+
+ Common::Vec3f rad_gyro = {};
+ const f32 ax = -normal_accel.x;
+ const f32 ay = normal_accel.y;
+ const f32 az = -normal_accel.z;
+
+ // Estimated direction of gravity
+ const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
+ const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
+ const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
+
+ // Error is cross product between estimated direction and measured direction of gravity
+ const Common::Vec3f new_real_error = {
+ az * vx - ax * vz,
+ ay * vz - az * vy,
+ ax * vy - ay * vx,
+ };
+
+ derivative_error = new_real_error - real_error;
+ real_error = new_real_error;
+
+ rad_gyro += 10.0f * kp * real_error;
+ rad_gyro += 5.0f * ki * integral_error;
+ rad_gyro += 10.0f * kd * derivative_error;
+
+ const f32 gx = rad_gyro.y;
+ const f32 gy = rad_gyro.x;
+ const f32 gz = rad_gyro.z;
+
+ // Integrate rate of change of quaternion
+ const f32 pa = q2;
+ const f32 pb = q3;
+ const f32 pc = q4;
+ q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
+ q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
+ q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
+ q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
+
+ quat.w = q1;
+ quat.xyz[0] = q2;
+ quat.xyz[1] = q3;
+ quat.xyz[2] = q4;
+ quat = quat.Normalized();
+ }
+}
+} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..6342d0318
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,73 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/quaternion.h"
+#include "common/vector_math.h"
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+class MotionInput {
+public:
+ MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
+
+ MotionInput(const MotionInput&) = default;
+ MotionInput& operator=(const MotionInput&) = default;
+
+ MotionInput(MotionInput&&) = default;
+ MotionInput& operator=(MotionInput&&) = default;
+
+ void SetAcceleration(const Common::Vec3f& acceleration);
+ void SetGyroscope(const Common::Vec3f& acceleration);
+ void SetQuaternion(const Common::Quaternion<f32>& quaternion);
+ void SetGyroDrift(const Common::Vec3f& drift);
+ void SetGyroThreshold(f32 threshold);
+
+ void EnableReset(bool reset);
+ void ResetRotations();
+
+ void UpdateRotation(u64 elapsed_time);
+ void UpdateOrientation(u64 elapsed_time);
+
+ std::array<Common::Vec3f, 3> GetOrientation() const;
+ Common::Vec3f GetAcceleration() const;
+ Common::Vec3f GetGyroscope() const;
+ Common::Vec3f GetRotations() const;
+ Common::Quaternion<f32> GetQuaternion() const;
+ Input::MotionStatus GetMotion() const;
+ Input::MotionStatus GetRandomMotion(int accel_magnitude, int gyro_magnitude) const;
+
+ bool IsMoving(f32 sensitivity) const;
+ bool IsCalibrated(f32 sensitivity) const;
+
+private:
+ void ResetOrientation();
+ void SetOrientationFromAccelerometer();
+
+ // PID constants
+ const f32 kp;
+ const f32 ki;
+ const f32 kd;
+
+ // PID errors
+ Common::Vec3f real_error;
+ Common::Vec3f integral_error;
+ Common::Vec3f derivative_error;
+
+ Common::Quaternion<f32> quat;
+ Common::Vec3f rotations;
+ Common::Vec3f accel;
+ Common::Vec3f gyro;
+ Common::Vec3f gyro_drift;
+
+ f32 gyro_threshold = 0.0f;
+ u32 reset_counter = 0;
+ bool reset_enabled = true;
+ bool only_accelerometer = true;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 5306daa70..f3554be9a 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -6,6 +6,7 @@
#include <memory>
#include <vector>
+#include "common/param_package.h"
#include "input_common/main.h"
namespace InputCommon::Polling {
@@ -22,14 +23,24 @@ public:
/// Unregisters SDL device factories and shut them down.
virtual ~State() = default;
- virtual Pollers GetPollers(Polling::DeviceType type) = 0;
+ virtual Pollers GetPollers(Polling::DeviceType type) {
+ return {};
+ }
+
+ virtual std::vector<Common::ParamPackage> GetInputDevices() {
+ return {};
+ }
+
+ virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
+ virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
+ return {};
+ }
};
class NullState : public State {
public:
- Pollers GetPollers(Polling::DeviceType type) override {
- return {};
- }
};
std::unique_ptr<State> Init();
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 675b477fa..bd480570a 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,10 +3,14 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <array>
#include <atomic>
+#include <chrono>
#include <cmath>
#include <functional>
#include <mutex>
+#include <optional>
+#include <sstream>
#include <string>
#include <thread>
#include <tuple>
@@ -15,15 +19,17 @@
#include <vector>
#include <SDL.h>
#include "common/logging/log.h"
-#include "common/math_util.h"
#include "common/param_package.h"
#include "common/threadsafe_queue.h"
#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
#include "input_common/sdl/sdl_impl.h"
+#include "input_common/settings.h"
namespace InputCommon::SDL {
-static std::string GetGUID(SDL_Joystick* joystick) {
+namespace {
+std::string GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
char guid_str[33];
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
@@ -31,7 +37,8 @@ static std::string GetGUID(SDL_Joystick* joystick) {
}
/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+} // Anonymous namespace
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
auto* const sdl_state = static_cast<SDLState*>(user_data);
@@ -48,8 +55,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
class SDLJoystick {
public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {}
+ SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
+ SDL_GameController* gamecontroller)
+ : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
+ sdl_controller{gamecontroller, &SDL_GameControllerClose} {}
void SetButton(int button, bool value) {
std::lock_guard lock{mutex};
@@ -66,14 +75,41 @@ public:
state.axes.insert_or_assign(axis, value);
}
- float GetAxis(int axis) const {
+ float GetAxis(int axis, float range) const {
std::lock_guard lock{mutex};
- return state.axes.at(axis) / 32767.0f;
+ return state.axes.at(axis) / (32767.0f * range);
}
- std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
+ bool RumblePlay(f32 amp_low, f32 amp_high, int time) {
+ const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF);
+ const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF);
+ // Lower drastically the number of state changes
+ if (raw_amp_low >> 11 == last_state_rumble_low >> 11 &&
+ raw_amp_high >> 11 == last_state_rumble_high >> 11) {
+ if (raw_amp_low + raw_amp_high != 0 ||
+ last_state_rumble_low + last_state_rumble_high == 0) {
+ return false;
+ }
+ }
+ // Don't change state if last vibration was < 20ms
+ const auto now = std::chrono::system_clock::now();
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) <
+ std::chrono::milliseconds(20)) {
+ return raw_amp_low + raw_amp_high == 0;
+ }
+
+ last_vibration = now;
+ last_state_rumble_low = raw_amp_low;
+ last_state_rumble_high = raw_amp_high;
+ if (sdl_joystick) {
+ SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time);
+ }
+ return false;
+ }
+
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
+ float x = GetAxis(axis_x, range);
+ float y = GetAxis(axis_y, range);
y = -y; // 3DS uses an y-axis inverse from SDL
// Make sure the coordinates are in the unit circle,
@@ -88,6 +124,10 @@ public:
return std::make_tuple(x, y);
}
+ const InputCommon::MotionInput& GetMotion() const {
+ return motion;
+ }
+
void SetHat(int hat, Uint8 direction) {
std::lock_guard lock{mutex};
state.hats.insert_or_assign(hat, direction);
@@ -115,10 +155,15 @@ public:
return sdl_joystick.get();
}
- void SetSDLJoystick(SDL_Joystick* joystick) {
+ void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
+ sdl_controller.reset(controller);
sdl_joystick.reset(joystick);
}
+ SDL_GameController* GetSDLGameController() const {
+ return sdl_controller.get();
+ }
+
private:
struct State {
std::unordered_map<int, bool> buttons;
@@ -127,8 +172,15 @@ private:
} state;
std::string guid;
int port;
+ u16 last_state_rumble_high;
+ u16 last_state_rumble_low;
+ std::chrono::time_point<std::chrono::system_clock> last_vibration;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
+ std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
+
+ // motion is initalized without PID values as motion input is not aviable for SDL2
+ InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
};
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
@@ -136,18 +188,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g
const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) {
- auto joystick =
- std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr);
+ auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
+ nullptr, nullptr);
it->second.emplace_back(std::move(joystick));
}
return it->second[port];
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
return joystick_map[guid].emplace_back(std::move(joystick));
}
std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
+ auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id);
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
@@ -171,32 +224,36 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
});
if (nullptr_it != map_it->second.end()) {
// ... and map it
- (*nullptr_it)->SetSDLJoystick(sdl_joystick);
+ (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller);
return *nullptr_it;
}
// There is no SDLJoystick without a mapped SDL_Joystick
// Create a new SDLJoystick
const int port = static_cast<int>(map_it->second.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller);
return map_it->second.emplace_back(std::move(joystick));
}
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller);
return joystick_map[guid].emplace_back(std::move(joystick));
}
void SDLState::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
+ SDL_GameController* sdl_gamecontroller = nullptr;
+ if (SDL_IsGameController(joystick_index)) {
+ sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
+ }
if (!sdl_joystick) {
- LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+ LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
return;
}
const std::string guid = GetGUID(sdl_joystick);
std::lock_guard lock{joystick_map_mutex};
if (joystick_map.find(guid) == joystick_map.end()) {
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
joystick_map[guid].emplace_back(std::move(joystick));
return;
}
@@ -205,11 +262,11 @@ void SDLState::InitJoystick(int joystick_index) {
joystick_guid_list.begin(), joystick_guid_list.end(),
[](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
if (it != joystick_guid_list.end()) {
- (*it)->SetSDLJoystick(sdl_joystick);
+ (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
return;
}
const int port = static_cast<int>(joystick_guid_list.size());
- auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
+ auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
joystick_guid_list.emplace_back(std::move(joystick));
}
@@ -231,7 +288,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
// Destruct SDL_Joystick outside the lock guard because SDL can internally call the
// event callback which locks the mutex again.
- joystick->SetSDLJoystick(nullptr);
+ joystick->SetSDLJoystick(nullptr, nullptr);
}
void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -285,6 +342,12 @@ public:
return joystick->GetButton(button);
}
+ bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
+ const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f));
+ const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f));
+ return joystick->RumblePlay(new_amp_low, new_amp_high, 250);
+ }
+
private:
std::shared_ptr<SDLJoystick> joystick;
int button;
@@ -313,7 +376,7 @@ public:
trigger_if_greater(trigger_if_greater_) {}
bool GetStatus() const override {
- const float axis_value = joystick->GetAxis(axis);
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
if (trigger_if_greater) {
return axis_value > threshold;
}
@@ -329,22 +392,24 @@ private:
class SDLAnalog final : public Input::AnalogDevice {
public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
+ SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_,
+ float range_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
+ range(range_) {}
std::tuple<float, float> GetStatus() const override {
- const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
+ const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
y / r * (r - deadzone) / (1 - deadzone));
}
- return std::make_tuple<float, float>(0.0f, 0.0f);
+ return {};
}
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
const auto [x, y] = GetStatus();
- const float directional_deadzone = 0.4f;
+ const float directional_deadzone = 0.5f;
switch (direction) {
case Input::AnalogDirection::RIGHT:
return x > directional_deadzone;
@@ -363,6 +428,69 @@ private:
const int axis_x;
const int axis_y;
const float deadzone;
+ const float range;
+};
+
+class SDLDirectionMotion final : public Input::MotionDevice {
+public:
+ explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetHatDirection(hat, direction)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAxisMotion final : public Input::MotionDevice {
+public:
+ explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
+ bool trigger_if_greater_)
+ : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
+ trigger_if_greater(trigger_if_greater_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ const float axis_value = joystick->GetAxis(axis, 1.0f);
+ bool trigger = axis_value < threshold;
+ if (trigger_if_greater) {
+ trigger = axis_value > threshold;
+ }
+
+ if (trigger) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis;
+ float threshold;
+ bool trigger_if_greater;
+};
+
+class SDLButtonMotion final : public Input::MotionDevice {
+public:
+ explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(std::move(joystick_)), button(button_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ if (joystick->GetButton(button)) {
+ return joystick->GetMotion().GetRandomMotion(2, 6);
+ }
+ return joystick->GetMotion().GetRandomMotion(0, 0);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
};
/// A button device factory that creates button devices from SDL joystick
@@ -457,14 +585,78 @@ public:
const int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
- const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
-
+ const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
+ const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
auto joystick = state.GetSDLJoystickByGUID(guid, port);
// This is necessary so accessing GetAxis with axis_x and axis_y won't crash
joystick->SetAxis(axis_x, 0);
joystick->SetAxis(axis_y, 0);
- return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
+ return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
+ }
+
+private:
+ SDLState& state;
+};
+
+/// A motion device factory that creates motion devices from SDL joystick
+class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+ explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates motion device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ */
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+
+ auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+ if (params.Has("hat")) {
+ const int hat = params.Get("hat", 0);
+ const std::string direction_name = params.Get("direction", "");
+ Uint8 direction;
+ if (direction_name == "up") {
+ direction = SDL_HAT_UP;
+ } else if (direction_name == "down") {
+ direction = SDL_HAT_DOWN;
+ } else if (direction_name == "left") {
+ direction = SDL_HAT_LEFT;
+ } else if (direction_name == "right") {
+ direction = SDL_HAT_RIGHT;
+ } else {
+ direction = 0;
+ }
+ // This is necessary so accessing GetHat with hat won't crash
+ joystick->SetHat(hat, SDL_HAT_CENTERED);
+ return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
+ }
+
+ if (params.Has("axis")) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.5f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ // This is necessary so accessing GetAxis with axis won't crash
+ joystick->SetAxis(axis, 0);
+ return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
+ }
+
+ const int button = params.Get("button", 0);
+ // This is necessary so accessing GetButton with button won't crash
+ joystick->SetButton(button, false);
+ return std::make_unique<SDLButtonMotion>(joystick, button);
}
private:
@@ -473,8 +665,12 @@ private:
SDLState::SDLState() {
using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
+ analog_factory = std::make_shared<SDLAnalogFactory>(*this);
+ button_factory = std::make_shared<SDLButtonFactory>(*this);
+ motion_factory = std::make_shared<SDLMotionFactory>(*this);
+ RegisterFactory<AnalogDevice>("sdl", analog_factory);
+ RegisterFactory<ButtonDevice>("sdl", button_factory);
+ RegisterFactory<MotionDevice>("sdl", motion_factory);
// If the frontend is going to manage the event loop, then we dont start one here
start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
@@ -482,6 +678,7 @@ SDLState::SDLState() {
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
return;
}
+ has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
}
@@ -494,7 +691,7 @@ SDLState::SDLState() {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
- std::this_thread::sleep_for(10ms);
+ std::this_thread::sleep_for(5ms);
}
});
}
@@ -509,6 +706,7 @@ SDLState::~SDLState() {
using namespace Input;
UnregisterFactory<ButtonDevice>("sdl");
UnregisterFactory<AnalogDevice>("sdl");
+ UnregisterFactory<MotionDevice>("sdl");
CloseJoysticks();
SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -520,65 +718,251 @@ SDLState::~SDLState() {
}
}
-static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
+ std::scoped_lock lock(joystick_map_mutex);
+ std::vector<Common::ParamPackage> devices;
+ for (const auto& [key, value] : joystick_map) {
+ for (const auto& joystick : value) {
+ auto joy = joystick->GetSDLJoystick();
+ if (auto controller = joystick->GetSDLGameController()) {
+ std::string name =
+ fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ } else if (joy) {
+ std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "sdl"},
+ {"display", std::move(name)},
+ {"guid", joystick->GetGUID()},
+ {"port", std::to_string(joystick->GetPort())},
+ });
+ }
+ }
+ }
+ return devices;
+}
+
+namespace {
+Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis,
+ float value = 0.1f) {
Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("axis", axis);
+ if (value > 0) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.5");
+ }
+ return params;
+}
+
+Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("button", button);
+ return params;
+}
+Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+
+ params.Set("port", port);
+ params.Set("guid", std::move(guid));
+ params.Set("hat", hat);
+ switch (value) {
+ case SDL_HAT_UP:
+ params.Set("direction", "up");
+ break;
+ case SDL_HAT_DOWN:
+ params.Set("direction", "down");
+ break;
+ case SDL_HAT_LEFT:
+ params.Set("direction", "left");
+ break;
+ case SDL_HAT_RIGHT:
+ params.Set("direction", "right");
+ break;
+ default:
+ return {};
+ }
+ return params;
+}
+
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
switch (event.type) {
case SDL_JOYAXISMOTION: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis", event.jaxis.axis);
- if (event.jaxis.value > 0) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.5");
- }
- break;
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jaxis.axis, event.jaxis.value);
}
case SDL_JOYBUTTONUP: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("button", event.jbutton.button);
- break;
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jbutton.button);
}
case SDL_JOYHATMOTION: {
const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("hat", event.jhat.hat);
- switch (event.jhat.value) {
- case SDL_HAT_UP:
- params.Set("direction", "up");
- break;
- case SDL_HAT_DOWN:
- params.Set("direction", "down");
- break;
- case SDL_HAT_LEFT:
- params.Set("direction", "left");
- break;
- case SDL_HAT_RIGHT:
- params.Set("direction", "right");
- break;
- default:
- return {};
- }
- break;
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jhat.hat, event.jhat.value);
+ }
+ }
+ return {};
+}
+
+Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jaxis.axis, event.jaxis.value);
}
+ case SDL_JOYBUTTONUP: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
+ return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jbutton.button);
}
+ case SDL_JOYHATMOTION: {
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
+ return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+ event.jhat.hat, event.jhat.value);
+ }
+ }
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
+ const SDL_GameControllerButtonBind& binding) {
+ switch (binding.bindType) {
+ case SDL_CONTROLLER_BINDTYPE_AXIS:
+ return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
+ case SDL_CONTROLLER_BINDTYPE_BUTTON:
+ return BuildButtonParamPackageForButton(port, guid, binding.value.button);
+ case SDL_CONTROLLER_BINDTYPE_HAT:
+ return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
+ binding.value.hat.hat_mask);
+ }
+ return {};
+}
+
+Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
+ int axis_y) {
+ Common::ParamPackage params;
+ params.Set("engine", "sdl");
+ params.Set("port", port);
+ params.Set("guid", guid);
+ params.Set("axis_x", axis_x);
+ params.Set("axis_y", axis_y);
return params;
}
+} // Anonymous namespace
-namespace Polling {
+ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+
+ // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
+ // We will add those afterwards
+ // This list also excludes Screenshot since theres not really a mapping for that
+ using ButtonBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
+ static constexpr ButtonBindings switch_to_sdl_button{{
+ {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
+ {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
+ {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
+ {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
+ {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
+ {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
+ {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
+ {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
+ {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
+ {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
+ {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
+ {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
+ {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
+ {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
+ {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+ }};
+
+ // Add the missing bindings for ZL/ZR
+ using ZBindings =
+ std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
+ static constexpr ZBindings switch_to_sdl_axis{{
+ {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
+ {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
+ }};
+
+ ButtonMapping mapping;
+ mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
+
+ for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
+ const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+ for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
+ const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
+ mapping.insert_or_assign(
+ switch_button,
+ BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
+ }
+
+ return mapping;
+}
+
+AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
+ if (!params.Has("guid") || !params.Has("port")) {
+ return {};
+ }
+ const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
+ auto* controller = joystick->GetSDLGameController();
+ if (controller == nullptr) {
+ return {};
+ }
+
+ AnalogMapping mapping = {};
+ const auto& binding_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
+ const auto& binding_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::LStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_left_x.value.axis,
+ binding_left_y.value.axis));
+ const auto& binding_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
+ const auto& binding_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
+ mapping.insert_or_assign(Settings::NativeAnalog::RStick,
+ BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ binding_right_x.value.axis,
+ binding_right_y.value.axis));
+ return mapping;
+}
+namespace Polling {
class SDLPoller : public InputCommon::Polling::DevicePoller {
public:
explicit SDLPoller(SDLState& state_) : state(state_) {}
- void Start() override {
+ void Start(const std::string& device_id) override {
state.event_queue.Clear();
state.polling = true;
}
@@ -598,71 +982,135 @@ public:
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- switch (event.type) {
- case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
- break;
- }
- [[fallthrough]];
- case SDL_JOYBUTTONUP:
- case SDL_JOYHATMOTION:
- return SDLEventToButtonParamPackage(state, event);
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
}
}
return {};
}
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToButtonParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
};
-class SDLAnalogPoller final : public SDLPoller {
+class SDLMotionPoller final : public SDLPoller {
public:
- explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
+ explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
- void Start() override {
- SDLPoller::Start();
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ const auto package = FromEvent(event);
+ if (package) {
+ return *package;
+ }
+ }
+ return {};
+ }
+ [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ [[fallthrough]];
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return {SDLEventToMotionParamPackage(state, event)};
+ }
+ return std::nullopt;
+ }
+};
+
+/**
+ * Attempts to match the press to a controller joy axis (left/right stick) and if a match
+ * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
+ * instead
+ */
+class SDLAnalogPreferredPoller final : public SDLPoller {
+public:
+ explicit SDLAnalogPreferredPoller(SDLState& state_)
+ : SDLPoller(state_), button_poller(state_) {}
+ void Start(const std::string& device_id) override {
+ SDLPoller::Start(device_id);
+ // Load the game controller
// Reset stored axes
analog_x_axis = -1;
analog_y_axis = -1;
- analog_axes_joystick = -1;
}
Common::ParamPackage GetNextInput() override {
SDL_Event event;
while (state.event_queue.Pop(event)) {
- if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ // Filter out axis events that are below a threshold
+ if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
continue;
}
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second SDL event. The axes also must be from the same joystick.
- const int axis = event.jaxis.axis;
- if (analog_x_axis == -1) {
- analog_x_axis = axis;
- analog_axes_joystick = event.jaxis.which;
- } else if (analog_y_axis == -1 && analog_x_axis != axis &&
- analog_axes_joystick == event.jaxis.which) {
- analog_y_axis = axis;
+ // Simplify controller config by testing if game controller support is enabled.
+ if (event.type == SDL_JOYAXISMOTION) {
+ const auto axis = event.jaxis.axis;
+ const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ const auto controller = joystick->GetSDLGameController();
+ if (controller) {
+ const auto axis_left_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
+ .value.axis;
+ const auto axis_left_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
+ .value.axis;
+ const auto axis_right_x =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
+ .value.axis;
+ const auto axis_right_y =
+ SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
+ .value.axis;
+
+ if (axis == axis_left_x || axis == axis_left_y) {
+ analog_x_axis = axis_left_x;
+ analog_y_axis = axis_left_y;
+ break;
+ } else if (axis == axis_right_x || axis == axis_right_y) {
+ analog_x_axis = axis_right_x;
+ analog_y_axis = axis_right_y;
+ break;
+ }
+ }
+ }
+
+ // If the press wasn't accepted as a joy axis, check for a button press
+ auto button_press = button_poller.FromEvent(event);
+ if (button_press) {
+ return *button_press;
}
}
- Common::ParamPackage params;
+
if (analog_x_axis != -1 && analog_y_axis != -1) {
const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("engine", "sdl");
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis_x", analog_x_axis);
- params.Set("axis_y", analog_y_axis);
+ auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
+ analog_x_axis, analog_y_axis);
analog_x_axis = -1;
analog_y_axis = -1;
- analog_axes_joystick = -1;
return params;
}
- return params;
+ return {};
}
private:
int analog_x_axis = -1;
int analog_y_axis = -1;
- SDL_JoystickID analog_axes_joystick = -1;
+ SDLButtonPoller button_poller;
};
} // namespace Polling
@@ -670,12 +1118,15 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
Pollers pollers;
switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
+ case InputCommon::Polling::DeviceType::AnalogPreferred:
+ pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
break;
case InputCommon::Polling::DeviceType::Button:
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
break;
+ case InputCommon::Polling::DeviceType::Motion:
+ pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
+ break;
}
return pollers;
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 606a32c5b..b9bb4dc56 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -21,6 +21,7 @@ namespace InputCommon::SDL {
class SDLAnalogFactory;
class SDLButtonFactory;
+class SDLMotionFactory;
class SDLJoystick;
class SDLState : public State {
@@ -50,6 +51,11 @@ public:
std::atomic<bool> polling = false;
Common::SPSCQueue<SDL_Event> event_queue;
+ std::vector<Common::ParamPackage> GetInputDevices() override;
+
+ ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+ AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+
private:
void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick);
@@ -57,12 +63,16 @@ private:
/// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks();
+ // Set to true if SDL supports game controller subsystem
+ bool has_gamecontroller = false;
+
/// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex;
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
+ std::shared_ptr<SDLMotionFactory> motion_factory;
bool start_thread = false;
std::atomic<bool> initialized = false;
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
new file mode 100644
index 000000000..b66c05856
--- /dev/null
+++ b/src/input_common/settings.cpp
@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "input_common/settings.h"
+
+namespace Settings {
+namespace NativeButton {
+const std::array<const char*, NumButtons> mapping = {{
+ "button_a", "button_b", "button_x", "button_y", "button_lstick",
+ "button_rstick", "button_l", "button_r", "button_zl", "button_zr",
+ "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
+ "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot",
+}};
+}
+
+namespace NativeMotion {
+const std::array<const char*, NumMotions> mapping = {{
+ "motionleft",
+ "motionright",
+}};
+}
+
+namespace NativeAnalog {
+const std::array<const char*, NumAnalogs> mapping = {{
+ "lstick",
+ "rstick",
+}};
+}
+
+namespace NativeMouseButton {
+const std::array<const char*, NumMouseButtons> mapping = {{
+ "left",
+ "right",
+ "middle",
+ "forward",
+ "back",
+}};
+}
+} // namespace Settings
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
new file mode 100644
index 000000000..ab0b95cf1
--- /dev/null
+++ b/src/input_common/settings.h
@@ -0,0 +1,352 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include "common/common_types.h"
+
+namespace Settings {
+namespace NativeButton {
+enum Values : int {
+ A,
+ B,
+ X,
+ Y,
+ LStick,
+ RStick,
+ L,
+ R,
+ ZL,
+ ZR,
+ Plus,
+ Minus,
+
+ DLeft,
+ DUp,
+ DRight,
+ DDown,
+
+ SL,
+ SR,
+
+ Home,
+ Screenshot,
+
+ NumButtons,
+};
+
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_NS_BEGIN = Home;
+
+constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
+
+extern const std::array<const char*, NumButtons> mapping;
+
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values : int {
+ LStick,
+ RStick,
+
+ NumAnalogs,
+};
+
+constexpr int STICK_HID_BEGIN = LStick;
+constexpr int STICK_HID_END = NumAnalogs;
+constexpr int NUM_STICKS_HID = NumAnalogs;
+
+extern const std::array<const char*, NumAnalogs> mapping;
+} // namespace NativeAnalog
+
+namespace NativeMotion {
+enum Values : int {
+ MOTIONLEFT,
+ MOTIONRIGHT,
+
+ NumMotions,
+};
+
+constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
+constexpr int MOTION_HID_END = NumMotions;
+constexpr int NUM_MOTION_HID = NumMotions;
+
+extern const std::array<const char*, NumMotions> mapping;
+} // namespace NativeMotion
+
+namespace NativeMouseButton {
+enum Values {
+ Left,
+ Right,
+ Middle,
+ Forward,
+ Back,
+
+ NumMouseButtons,
+};
+
+constexpr int MOUSE_HID_BEGIN = Left;
+constexpr int MOUSE_HID_END = NumMouseButtons;
+constexpr int NUM_MOUSE_HID = NumMouseButtons;
+
+extern const std::array<const char*, NumMouseButtons> mapping;
+} // namespace NativeMouseButton
+
+namespace NativeKeyboard {
+enum Keys {
+ None,
+ Error,
+
+ A = 4,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+ H,
+ I,
+ J,
+ K,
+ L,
+ M,
+ N,
+ O,
+ P,
+ Q,
+ R,
+ S,
+ T,
+ U,
+ V,
+ W,
+ X,
+ Y,
+ Z,
+ N1,
+ N2,
+ N3,
+ N4,
+ N5,
+ N6,
+ N7,
+ N8,
+ N9,
+ N0,
+ Enter,
+ Escape,
+ Backspace,
+ Tab,
+ Space,
+ Minus,
+ Equal,
+ LeftBrace,
+ RightBrace,
+ Backslash,
+ Tilde,
+ Semicolon,
+ Apostrophe,
+ Grave,
+ Comma,
+ Dot,
+ Slash,
+ CapsLockKey,
+
+ F1,
+ F2,
+ F3,
+ F4,
+ F5,
+ F6,
+ F7,
+ F8,
+ F9,
+ F10,
+ F11,
+ F12,
+
+ SystemRequest,
+ ScrollLockKey,
+ Pause,
+ Insert,
+ Home,
+ PageUp,
+ Delete,
+ End,
+ PageDown,
+ Right,
+ Left,
+ Down,
+ Up,
+
+ NumLockKey,
+ KPSlash,
+ KPAsterisk,
+ KPMinus,
+ KPPlus,
+ KPEnter,
+ KP1,
+ KP2,
+ KP3,
+ KP4,
+ KP5,
+ KP6,
+ KP7,
+ KP8,
+ KP9,
+ KP0,
+ KPDot,
+
+ Key102,
+ Compose,
+ Power,
+ KPEqual,
+
+ F13,
+ F14,
+ F15,
+ F16,
+ F17,
+ F18,
+ F19,
+ F20,
+ F21,
+ F22,
+ F23,
+ F24,
+
+ Open,
+ Help,
+ Properties,
+ Front,
+ Stop,
+ Repeat,
+ Undo,
+ Cut,
+ Copy,
+ Paste,
+ Find,
+ Mute,
+ VolumeUp,
+ VolumeDown,
+ CapsLockActive,
+ NumLockActive,
+ ScrollLockActive,
+ KPComma,
+
+ KPLeftParenthesis,
+ KPRightParenthesis,
+
+ LeftControlKey = 0xE0,
+ LeftShiftKey,
+ LeftAltKey,
+ LeftMetaKey,
+ RightControlKey,
+ RightShiftKey,
+ RightAltKey,
+ RightMetaKey,
+
+ MediaPlayPause,
+ MediaStopCD,
+ MediaPrevious,
+ MediaNext,
+ MediaEject,
+ MediaVolumeUp,
+ MediaVolumeDown,
+ MediaMute,
+ MediaWebsite,
+ MediaBack,
+ MediaForward,
+ MediaStop,
+ MediaFind,
+ MediaScrollUp,
+ MediaScrollDown,
+ MediaEdit,
+ MediaSleep,
+ MediaCoffee,
+ MediaRefresh,
+ MediaCalculator,
+
+ NumKeyboardKeys,
+};
+
+static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
+
+enum Modifiers {
+ LeftControl,
+ LeftShift,
+ LeftAlt,
+ LeftMeta,
+ RightControl,
+ RightShift,
+ RightAlt,
+ RightMeta,
+ CapsLock,
+ ScrollLock,
+ NumLock,
+
+ NumKeyboardMods,
+};
+
+constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
+constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
+constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
+
+constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
+constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
+constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
+
+} // namespace NativeKeyboard
+
+using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
+using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
+using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
+using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
+using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
+
+constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
+constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
+constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
+constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
+
+enum class ControllerType {
+ ProController,
+ DualJoyconDetached,
+ LeftJoycon,
+ RightJoycon,
+ Handheld,
+};
+
+struct PlayerInput {
+ bool connected;
+ ControllerType controller_type;
+ ButtonsRaw buttons;
+ AnalogsRaw analogs;
+ MotionRaw motions;
+ std::string lstick_mod;
+ std::string rstick_mod;
+
+ u32 body_color_left;
+ u32 body_color_right;
+ u32 button_color_left;
+ u32 button_color_right;
+};
+
+struct TouchscreenInput {
+ bool enabled;
+ std::string device;
+
+ u32 finger;
+ u32 diameter_x;
+ u32 diameter_y;
+ u32 rotation_angle;
+};
+} // namespace Settings
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..98da0ef1a
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,50 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/framebuffer_layout.h"
+#include "core/settings.h"
+#include "input_common/touch_from_button.h"
+
+namespace InputCommon {
+
+class TouchFromButtonDevice final : public Input::TouchDevice {
+public:
+ TouchFromButtonDevice() {
+ for (const auto& config_entry :
+ Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
+ .buttons) {
+ const Common::ParamPackage package{config_entry};
+ map.emplace_back(
+ Input::CreateDevice<Input::ButtonDevice>(config_entry),
+ std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
+ std::clamp(package.Get("y", 0), 0,
+ static_cast<int>(Layout::ScreenUndocked::Height)));
+ }
+ }
+
+ std::tuple<float, float, bool> GetStatus() const override {
+ for (const auto& m : map) {
+ const bool state = std::get<0>(m)->GetStatus();
+ if (state) {
+ const float x = static_cast<float>(std::get<1>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Width);
+ const float y = static_cast<float>(std::get<2>(m)) /
+ static_cast<int>(Layout::ScreenUndocked::Height);
+ return {x, y, true};
+ }
+ }
+ return {};
+ }
+
+private:
+ // A vector of the mapped button, its x and its y-coordinate
+ std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
+};
+
+std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
+ const Common::ParamPackage& params) {
+ return std::make_unique<TouchFromButtonDevice>();
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+/**
+ * A touch device factory that takes a list of button devices and combines them into a touch device.
+ */
+class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ /**
+ * Creates a touch device from a list of button devices
+ */
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index e63c73c4f..9d0b9f31d 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,15 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <array>
#include <chrono>
#include <cstring>
#include <functional>
#include <thread>
#include <boost/asio.hpp>
-#include <boost/bind.hpp>
#include "common/logging/log.h"
+#include "core/settings.h"
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
@@ -132,21 +130,59 @@ static void SocketLoop(Socket* socket) {
socket->Loop();
}
-Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
- u8 pad_index, u32 client_id)
- : status(std::move(status)) {
- StartCommunication(host, port, pad_index, client_id);
+Client::Client() {
+ LOG_INFO(Input, "Udp Initialization started");
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ u8 pad = client % 4;
+ StartCommunication(client, Settings::values.udp_input_address,
+ Settings::values.udp_input_port, pad, 24872);
+ // Set motion parameters
+ // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
+ // Real HW values are unknown, 0.0001 is an approximate to Standard
+ clients[client].motion.SetGyroThreshold(0.0001f);
+ }
}
Client::~Client() {
- socket->Stop();
- thread.join();
+ Reset();
+}
+
+std::vector<Common::ParamPackage> Client::GetInputDevices() const {
+ std::vector<Common::ParamPackage> devices;
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ if (!DeviceConnected(client)) {
+ continue;
+ }
+ std::string name = fmt::format("UDP Controller {}", client);
+ devices.emplace_back(Common::ParamPackage{
+ {"class", "cemuhookudp"},
+ {"display", std::move(name)},
+ {"port", std::to_string(client)},
+ });
+ }
+ return devices;
}
+bool Client::DeviceConnected(std::size_t pad) const {
+ // Use last timestamp to detect if the socket has stopped sending data
+ const auto now = std::chrono::system_clock::now();
+ u64 time_difference =
+ std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
+ .count();
+ return time_difference < 1000 && clients[pad].active == 1;
+}
+
+void Client::ReloadUDPClient() {
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
+ }
+}
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
- socket->Stop();
- thread.join();
- StartCommunication(host, port, pad_index, client_id);
+ // client number must be determined from host / port and pad index
+ std::size_t client = pad_index;
+ clients[client].socket->Stop();
+ clients[client].thread.join();
+ StartCommunication(client, host, port, pad_index, client_id);
}
void Client::OnVersion(Response::Version data) {
@@ -158,23 +194,35 @@ void Client::OnPortInfo(Response::PortInfo data) {
}
void Client::OnPadData(Response::PadData data) {
+ // client number must be determined from host / port and pad index
+ std::size_t client = data.info.id;
LOG_TRACE(Input, "PadData packet received");
- if (data.packet_counter <= packet_sequence) {
+ if (data.packet_counter == clients[client].packet_sequence) {
LOG_WARNING(
Input,
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
- packet_sequence, data.packet_counter);
+ clients[client].packet_sequence, data.packet_counter);
return;
}
- packet_sequence = data.packet_counter;
- // TODO: Check how the Switch handles motions and how the CemuhookUDP motion
- // directions correspond to the ones of the Switch
- Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
- Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
- {
- std::lock_guard guard(status->update_mutex);
+ clients[client].active = data.info.is_pad_active;
+ clients[client].packet_sequence = data.packet_counter;
+ const auto now = std::chrono::system_clock::now();
+ u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
+ now - clients[client].last_motion_update)
+ .count();
+ clients[client].last_motion_update = now;
+ Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
+ clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
+ // Gyroscope values are not it the correct scale from better joy.
+ // Dividing by 312 allows us to make one full turn = 1 turn
+ // This must be a configurable valued called sensitivity
+ clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
+ clients[client].motion.UpdateRotation(time_difference);
+ clients[client].motion.UpdateOrientation(time_difference);
- status->motion_status = {accel, gyro};
+ {
+ std::lock_guard guard(clients[client].status.update_mutex);
+ clients[client].status.motion_status = clients[client].motion.GetMotion();
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
// between a simple "tap" and a hard press that causes the touch screen to click.
@@ -183,11 +231,11 @@ void Client::OnPadData(Response::PadData data) {
float x = 0;
float y = 0;
- if (is_active && status->touch_calibration) {
- const u16 min_x = status->touch_calibration->min_x;
- const u16 max_x = status->touch_calibration->max_x;
- const u16 min_y = status->touch_calibration->min_y;
- const u16 max_y = status->touch_calibration->max_y;
+ if (is_active && clients[client].status.touch_calibration) {
+ const u16 min_x = clients[client].status.touch_calibration->min_x;
+ const u16 max_x = clients[client].status.touch_calibration->max_x;
+ const u16 min_y = clients[client].status.touch_calibration->min_y;
+ const u16 max_y = clients[client].status.touch_calibration->max_y;
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
static_cast<float>(max_x - min_x);
@@ -195,17 +243,86 @@ void Client::OnPadData(Response::PadData data) {
static_cast<float>(max_y - min_y);
}
- status->touch_status = {x, y, is_active};
+ clients[client].status.touch_status = {x, y, is_active};
+
+ if (configuring) {
+ const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
+ const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
+ UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
+ }
}
}
-void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
+void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
+ u32 client_id) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this](Response::PadData data) { OnPadData(data); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
- socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
- thread = std::thread{SocketLoop, this->socket.get()};
+ clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
+ clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
+}
+
+void Client::Reset() {
+ for (std::size_t client = 0; client < clients.size(); client++) {
+ clients[client].socket->Stop();
+ clients[client].thread.join();
+ }
+}
+
+void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro, bool touch) {
+ if (gyro.Length() > 0.2f) {
+ LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {}), touch={}",
+ client, gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2], touch);
+ }
+ UDPPadStatus pad;
+ if (touch) {
+ pad.touch = PadTouch::Click;
+ pad_queue[client].Push(pad);
+ }
+ for (size_t i = 0; i < 3; ++i) {
+ if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
+ pad.motion = static_cast<PadMotion>(i);
+ pad.motion_value = gyro[i];
+ pad_queue[client].Push(pad);
+ }
+ if (acc[i] > 1.75f || acc[i] < -1.75f) {
+ pad.motion = static_cast<PadMotion>(i + 3);
+ pad.motion_value = acc[i];
+ pad_queue[client].Push(pad);
+ }
+ }
+}
+
+void Client::BeginConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = true;
+}
+
+void Client::EndConfiguration() {
+ for (auto& pq : pad_queue) {
+ pq.Clear();
+ }
+ configuring = false;
+}
+
+DeviceStatus& Client::GetPadState(std::size_t pad) {
+ return clients[pad].status;
+}
+
+const DeviceStatus& Client::GetPadState(std::size_t pad) const {
+ return clients[pad].status;
+}
+
+std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
+ return pad_queue;
+}
+
+const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
+ return pad_queue;
}
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
@@ -225,8 +342,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
} else {
failure_callback();
}
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
@@ -280,8 +396,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
complete_event.Wait();
socket.Stop();
worker_thread.join();
- })
- .detach();
+ }).detach();
}
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..523dc6a7a 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,8 +12,12 @@
#include <thread>
#include <tuple>
#include "common/common_types.h"
+#include "common/param_package.h"
#include "common/thread.h"
+#include "common/threadsafe_queue.h"
#include "common/vector_math.h"
+#include "core/frontend/input.h"
+#include "input_common/motion_input.h"
namespace InputCommon::CemuhookUDP {
@@ -28,9 +32,30 @@ struct PortInfo;
struct Version;
} // namespace Response
+enum class PadMotion {
+ GyroX,
+ GyroY,
+ GyroZ,
+ AccX,
+ AccY,
+ AccZ,
+ Undefined,
+};
+
+enum class PadTouch {
+ Click,
+ Undefined,
+};
+
+struct UDPPadStatus {
+ PadTouch touch{PadTouch::Undefined};
+ PadMotion motion{PadMotion::Undefined};
+ f32 motion_value{0.0f};
+};
+
struct DeviceStatus {
std::mutex update_mutex;
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
+ Input::MotionStatus motion_status;
std::tuple<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
@@ -45,22 +70,58 @@ struct DeviceStatus {
class Client {
public:
- explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
- u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
+ // Initialize the UDP client capture and read sequence
+ Client();
+
+ // Close and release the client
~Client();
+
+ // Used for polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ std::vector<Common::ParamPackage> GetInputDevices() const;
+
+ bool DeviceConnected(std::size_t pad) const;
+ void ReloadUDPClient();
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
u32 client_id = 24872);
+ std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
+ const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
+
+ DeviceStatus& GetPadState(std::size_t pad);
+ const DeviceStatus& GetPadState(std::size_t pad) const;
+
private:
+ struct ClientData {
+ std::unique_ptr<Socket> socket;
+ DeviceStatus status;
+ std::thread thread;
+ u64 packet_sequence = 0;
+ u8 active;
+
+ // Realtime values
+ // motion is initalized with PID values for drift correction on joycons
+ InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
+ std::chrono::time_point<std::chrono::system_clock> last_motion_update;
+ };
+
+ // For shutting down, clear all data, join all threads, release usb
+ void Reset();
+
void OnVersion(Response::Version);
void OnPortInfo(Response::PortInfo);
void OnPadData(Response::PadData);
- void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
+ void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
+ u32 client_id);
+ void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
+ const Common::Vec3<float>& gyro, bool touch);
+
+ bool configuring = false;
- std::unique_ptr<Socket> socket;
- std::shared_ptr<DeviceStatus> status;
- std::thread thread;
- u64 packet_sequence = 0;
+ std::array<ClientData, 4> clients;
+ std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
};
/// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 8c6ef1394..eba077a36 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,99 +1,144 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <atomic>
+#include <list>
#include <mutex>
-#include <optional>
-#include <tuple>
-
-#include "common/param_package.h"
-#include "core/frontend/input.h"
-#include "core/settings.h"
+#include <utility>
+#include "common/assert.h"
+#include "common/threadsafe_queue.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class UDPTouchDevice final : public Input::TouchDevice {
+class UDPMotion final : public Input::MotionDevice {
public:
- explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<float, float, bool> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->touch_status;
+ UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
+ : ip(ip_), port(port_), pad(pad_), client(client_) {}
+
+ Input::MotionStatus GetStatus() const override {
+ return client->GetPadState(pad).motion_status;
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ const int port;
+ const int pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-class UDPMotionDevice final : public Input::MotionDevice {
-public:
- explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
- std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
- std::lock_guard guard(status->update_mutex);
- return status->motion_status;
- }
+/// A motion device factory that creates motion devices from JC Adapter
+UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
+ const std::string ip = params.Get("ip", "127.0.0.1");
+ const int port = params.Get("port", 26760);
+ const int pad = params.Get("pad_index", 0);
+
+ return std::make_unique<UDPMotion>(ip, port, pad, client.get());
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
+void UDPMotionFactory::BeginConfiguration() {
+ polling = true;
+ client->BeginConfiguration();
+}
-class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
-public:
- explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
-
- std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
- {
- std::lock_guard guard(status->update_mutex);
- status->touch_calibration = DeviceStatus::CalibrationData{};
- // These default values work well for DS4 but probably not other touch inputs
- status->touch_calibration->min_x = params.Get("min_x", 100);
- status->touch_calibration->min_y = params.Get("min_y", 50);
- status->touch_calibration->max_x = params.Get("max_x", 1800);
- status->touch_calibration->max_y = params.Get("max_y", 850);
+void UDPMotionFactory::EndConfiguration() {
+ polling = false;
+ client->EndConfiguration();
+}
+
+Common::ParamPackage UDPMotionFactory::GetNextInput() {
+ Common::ParamPackage params;
+ CemuhookUDP::UDPPadStatus pad;
+ auto& queue = client->GetPadQueue();
+ for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+ while (queue[pad_number].Pop(pad)) {
+ if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
+ continue;
+ }
+ params.Set("engine", "cemuhookudp");
+ params.Set("ip", "127.0.0.1");
+ params.Set("port", 26760);
+ params.Set("pad_index", static_cast<int>(pad_number));
+ params.Set("motion", static_cast<u16>(pad.motion));
+ return params;
}
- return std::make_unique<UDPTouchDevice>(status);
}
+ return params;
+}
-private:
- std::shared_ptr<DeviceStatus> status;
-};
-
-class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
+class UDPTouch final : public Input::TouchDevice {
public:
- explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
+ UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
+ : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
- std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
- return std::make_unique<UDPMotionDevice>(status);
+ std::tuple<float, float, bool> GetStatus() const override {
+ return client->GetPadState(pad).touch_status;
}
private:
- std::shared_ptr<DeviceStatus> status;
+ const std::string ip;
+ const int port;
+ const int pad;
+ CemuhookUDP::Client* client;
+ mutable std::mutex mutex;
};
-State::State() {
- auto status = std::make_shared<DeviceStatus>();
- client =
- std::make_unique<Client>(status, Settings::values.udp_input_address,
- Settings::values.udp_input_port, Settings::values.udp_pad_index);
-
- Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
- std::make_shared<UDPTouchFactory>(status));
- Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
- std::make_shared<UDPMotionFactory>(status));
+/// A motion device factory that creates motion devices from JC Adapter
+UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
+ : client(std::move(client_)) {}
+
+/**
+ * Creates motion device
+ * @param params contains parameters for creating the device:
+ * - "port": the nth jcpad on the adapter
+ */
+std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
+ const std::string ip = params.Get("ip", "127.0.0.1");
+ const int port = params.Get("port", 26760);
+ const int pad = params.Get("pad_index", 0);
+
+ return std::make_unique<UDPTouch>(ip, port, pad, client.get());
}
-State::~State() {
- Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
- Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
+void UDPTouchFactory::BeginConfiguration() {
+ polling = true;
+ client->BeginConfiguration();
}
-void State::ReloadUDPClient() {
- client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
- Settings::values.udp_pad_index);
+void UDPTouchFactory::EndConfiguration() {
+ polling = false;
+ client->EndConfiguration();
}
-std::unique_ptr<State> Init() {
- return std::make_unique<State>();
+Common::ParamPackage UDPTouchFactory::GetNextInput() {
+ Common::ParamPackage params;
+ CemuhookUDP::UDPPadStatus pad;
+ auto& queue = client->GetPadQueue();
+ for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
+ while (queue[pad_number].Pop(pad)) {
+ if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
+ continue;
+ }
+ params.Set("engine", "cemuhookudp");
+ params.Set("ip", "127.0.0.1");
+ params.Set("port", 26760);
+ params.Set("pad_index", static_cast<int>(pad_number));
+ params.Set("touch", static_cast<u16>(pad.touch));
+ return params;
+ }
+ }
+ return params;
}
-} // namespace InputCommon::CemuhookUDP
+
+} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 4f83f0441..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,25 +1,57 @@
-// Copyright 2018 Citra Emulator Project
+// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
+#include "core/frontend/input.h"
+#include "input_common/udp/client.h"
-namespace InputCommon::CemuhookUDP {
+namespace InputCommon {
-class Client;
-
-class State {
+/// A motion device factory that creates motion devices from udp clients
+class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
- State();
- ~State();
- void ReloadUDPClient();
+ explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
private:
- std::unique_ptr<Client> client;
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
};
-std::unique_ptr<State> Init();
+/// A touch device factory that creates touch devices from udp clients
+class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
+public:
+ explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
+
+ std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
+
+ Common::ParamPackage GetNextInput();
+
+ /// For device input configuration/polling
+ void BeginConfiguration();
+ void EndConfiguration();
+
+ bool IsPolling() const {
+ return polling;
+ }
+
+private:
+ std::shared_ptr<CemuhookUDP::Client> client;
+ bool polling = false;
+};
-} // namespace InputCommon::CemuhookUDP
+} // namespace InputCommon