diff options
Diffstat (limited to '')
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 350 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 116 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_poller.cpp | 310 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_poller.h | 59 |
4 files changed, 835 insertions, 0 deletions
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..d42261d61 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -0,0 +1,350 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. +//* +#include "common/logging/log.h" +#include "common/threadsafe_queue.h" +#include "input_common/gcadapter/gc_adapter.h" + +Common::SPSCQueue<GCPadStatus> pad_queue[4]; +struct GCState state[4]; + +namespace GCAdapter { + +static libusb_device_handle* usb_adapter_handle = nullptr; +static u8 adapter_controllers_status[4] = { + ControllerTypes::CONTROLLER_NONE, ControllerTypes::CONTROLLER_NONE, + ControllerTypes::CONTROLLER_NONE, ControllerTypes::CONTROLLER_NONE}; + +static std::mutex s_mutex; + +static std::thread adapter_input_thread; +static bool adapter_thread_running; + +static std::mutex initialization_mutex; +static std::thread detect_thread; +static bool detect_thread_running = false; + +static libusb_context* libusb_ctx; + +static u8 input_endpoint = 0; + +static bool configuring = false; + +GCPadStatus CheckStatus(int port, u8 adapter_payload[37]) { + GCPadStatus pad = {}; + bool get_origin = false; + + u8 type = adapter_payload[1 + (9 * port)] >> 4; + if (type) + get_origin = true; + + adapter_controllers_status[port] = type; + + if (adapter_controllers_status[port] != ControllerTypes::CONTROLLER_NONE) { + u8 b1 = adapter_payload[1 + (9 * port) + 1]; + u8 b2 = adapter_payload[1 + (9 * port) + 2]; + + if (b1 & (1 << 0)) + pad.button |= PAD_BUTTON_A; + if (b1 & (1 << 1)) + pad.button |= PAD_BUTTON_B; + if (b1 & (1 << 2)) + pad.button |= PAD_BUTTON_X; + if (b1 & (1 << 3)) + pad.button |= PAD_BUTTON_Y; + + if (b1 & (1 << 4)) + pad.button |= PAD_BUTTON_LEFT; + if (b1 & (1 << 5)) + pad.button |= PAD_BUTTON_RIGHT; + if (b1 & (1 << 6)) + pad.button |= PAD_BUTTON_DOWN; + if (b1 & (1 << 7)) + pad.button |= PAD_BUTTON_UP; + + if (b2 & (1 << 0)) + pad.button |= PAD_BUTTON_START; + if (b2 & (1 << 1)) + pad.button |= PAD_TRIGGER_Z; + if (b2 & (1 << 2)) + pad.button |= PAD_TRIGGER_R; + if (b2 & (1 << 3)) + pad.button |= PAD_TRIGGER_L; + + if (get_origin) + pad.button |= PAD_GET_ORIGIN; + + pad.stickX = adapter_payload[1 + (9 * port) + 3]; + pad.stickY = adapter_payload[1 + (9 * port) + 4]; + pad.substickX = adapter_payload[1 + (9 * port) + 5]; + pad.substickY = adapter_payload[1 + (9 * port) + 6]; + pad.triggerLeft = adapter_payload[1 + (9 * port) + 7]; + pad.triggerRight = adapter_payload[1 + (9 * port) + 8]; + } + return pad; +} + +void PadToState(GCPadStatus pad, GCState& state) { + //std::lock_guard lock{s_mutex}; + state.buttons.insert_or_assign(PAD_BUTTON_A, pad.button & PAD_BUTTON_A); + state.buttons.insert_or_assign(PAD_BUTTON_B, pad.button & PAD_BUTTON_B); + state.buttons.insert_or_assign(PAD_BUTTON_X, pad.button & PAD_BUTTON_X); + state.buttons.insert_or_assign(PAD_BUTTON_Y, pad.button & PAD_BUTTON_Y); + state.buttons.insert_or_assign(PAD_BUTTON_LEFT, pad.button & PAD_BUTTON_LEFT); + state.buttons.insert_or_assign(PAD_BUTTON_RIGHT, pad.button & PAD_BUTTON_RIGHT); + state.buttons.insert_or_assign(PAD_BUTTON_DOWN, pad.button & PAD_BUTTON_DOWN); + state.buttons.insert_or_assign(PAD_BUTTON_UP, pad.button & PAD_BUTTON_UP); + state.buttons.insert_or_assign(PAD_BUTTON_START, pad.button & PAD_BUTTON_START); + state.buttons.insert_or_assign(PAD_TRIGGER_Z, pad.button & PAD_TRIGGER_Z); + state.buttons.insert_or_assign(PAD_TRIGGER_L, pad.button & PAD_TRIGGER_L); + state.buttons.insert_or_assign(PAD_TRIGGER_R, pad.button & PAD_TRIGGER_R); + state.axes.insert_or_assign(STICK_X, pad.stickX); + state.axes.insert_or_assign(STICK_Y, pad.stickY); + state.axes.insert_or_assign(SUBSTICK_X, pad.substickX); + state.axes.insert_or_assign(SUBSTICK_Y, pad.substickY); + state.axes.insert_or_assign(TRIGGER_LEFT, pad.triggerLeft); + state.axes.insert_or_assign(TRIGGER_RIGHT, pad.triggerRight); +} + +static void Read() { + LOG_INFO(Input, "GC Adapter Read() thread started"); + + int payload_size_in; + u8 adapter_payload[37]; + while (adapter_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload, + sizeof(adapter_payload), &payload_size_in, 32); + + int payload_size = 0; + u8 controller_payload_copy[37]; + + { + std::lock_guard<std::mutex> lk(s_mutex); + std::copy(std::begin(adapter_payload), std::end(adapter_payload), + std::begin(controller_payload_copy)); + payload_size = payload_size_in; + } + + GCPadStatus pad[4]; + if (payload_size != sizeof(controller_payload_copy) || + controller_payload_copy[0] != LIBUSB_DT_HID) { + LOG_ERROR(Input, "error reading payload (size: %d, type: %02x)", payload_size, + controller_payload_copy[0]); + } else { + for (int i = 0; i < 4; i++) + pad[i] = CheckStatus(i, controller_payload_copy); + } + for (int port = 0; port < 4; port++) { + if (DeviceConnected(port) && configuring) { + if (pad[port].button != PAD_GET_ORIGIN) + pad_queue[port].Push(pad[port]); + + // Accounting for a threshold here because of some controller variance + if (pad[port].stickX > pad[port].MAIN_STICK_CENTER_X + pad[port].THRESHOLD || + pad[port].stickX < pad[port].MAIN_STICK_CENTER_X - pad[port].THRESHOLD) { + pad[port].axis_which = STICK_X; + pad[port].axis_value = pad[port].stickX; + pad_queue[port].Push(pad[port]); + } + if (pad[port].stickY > pad[port].MAIN_STICK_CENTER_Y + pad[port].THRESHOLD || + pad[port].stickY < pad[port].MAIN_STICK_CENTER_Y - pad[port].THRESHOLD) { + pad[port].axis_which = STICK_Y; + pad[port].axis_value = pad[port].stickY; + pad_queue[port].Push(pad[port]); + } + if (pad[port].substickX > pad[port].C_STICK_CENTER_X + pad[port].THRESHOLD || + pad[port].substickX < pad[port].C_STICK_CENTER_X - pad[port].THRESHOLD) { + pad[port].axis_which = SUBSTICK_X; + pad[port].axis_value = pad[port].substickX; + pad_queue[port].Push(pad[port]); + } + if (pad[port].substickY > pad[port].C_STICK_CENTER_Y + pad[port].THRESHOLD || + pad[port].substickY < pad[port].C_STICK_CENTER_Y - pad[port].THRESHOLD) { + pad[port].axis_which = SUBSTICK_Y; + pad[port].axis_value = pad[port].substickY; + pad_queue[port].Push(pad[port]); + } + } + PadToState(pad[port], state[port]); + } + std::this_thread::yield(); + } +} + +static void 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(); + } + Sleep(500); + } +} + +void Init() { + + if (usb_adapter_handle != nullptr) + return; + LOG_INFO(Input, "GC Adapter Initialization started"); + + current_status = NO_ADAPTER_DETECTED; + libusb_init(&libusb_ctx); + + StartScanThread(); +} + +void StartScanThread() { + if (detect_thread_running) + return; + if (!libusb_ctx) + return; + + detect_thread_running = true; + detect_thread = std::thread(ScanThreadFunc); +} + +void StopScanThread() { + detect_thread.join(); +} + +static void Setup() { + // Reset the error status in case the adapter gets unplugged + if (current_status < 0) + current_status = NO_ADAPTER_DETECTED; + + for (int i = 0; i < 4; i++) + adapter_controllers_status[i] = ControllerTypes::CONTROLLER_NONE; + + libusb_device** devs; // pointer to list of connected usb devices + + int cnt = libusb_get_device_list(libusb_ctx, &devs); //get the list of devices + + for (int i = 0; i < cnt; i++) { + if (CheckDeviceAccess(devs[i])) { + // GC Adapter found, registering it + GetGCEndpoint(devs[i]); + break; + } + } +} + +static bool CheckDeviceAccess(libusb_device* device) { + libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(device, &desc); + if (ret) { + // could not acquire the descriptor, no point in trying to use it. + LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: %d", ret); + return false; + } + + if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { + // This isn’t the device we are looking for. + return false; + } + ret = libusb_open(device, &usb_adapter_handle); + + if (ret == LIBUSB_ERROR_ACCESS) { + LOG_ERROR(Input, + "Yuzu can not gain access to this device: ID %04X:%04X.", + desc.idVendor, desc.idProduct); + return false; + } + if (ret) { + LOG_ERROR(Input, "libusb_open failed to open device with error = %d", ret); + return false; + } + + ret = libusb_kernel_driver_active(usb_adapter_handle, 0); + if (ret == 1) { + ret = libusb_detach_kernel_driver(usb_adapter_handle, 0); + if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) + LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = %d", ret); + } + + if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) { + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + ret = libusb_claim_interface(usb_adapter_handle, 0); + if (ret) { + LOG_ERROR(Input, "libusb_claim_interface failed with error = %d", ret); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + return true; +} + +static void GetGCEndpoint(libusb_device* device) { + libusb_config_descriptor* config = nullptr; + libusb_get_config_descriptor(device, 0, &config); + for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { + const libusb_interface* interfaceContainer = &config->interface[ic]; + for (int i = 0; i < interfaceContainer->num_altsetting; i++) { + const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; + for (u8 e = 0; e < interface->bNumEndpoints; e++) { + const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; + if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) + input_endpoint = endpoint->bEndpointAddress; + } + } + } + + adapter_thread_running = true; + current_status = ADAPTER_DETECTED; + + adapter_input_thread = std::thread(Read); // Read input +} + +void Shutdown() { + StopScanThread(); + Reset(); + + current_status = NO_ADAPTER_DETECTED; +} + +static void 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_input_thread.join(); + + for (int i = 0; i < 4; i++) + adapter_controllers_status[i] = ControllerTypes::CONTROLLER_NONE; + + current_status = NO_ADAPTER_DETECTED; + + if (usb_adapter_handle) { + libusb_release_interface(usb_adapter_handle, 0); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + } +} + +bool DeviceConnected(int port) { + return adapter_controllers_status[port] != ControllerTypes::CONTROLLER_NONE; +} + +void ResetDeviceType(int port) { + adapter_controllers_status[port] = ControllerTypes::CONTROLLER_NONE; +} + +void BeginConfiguration() { + configuring = true; +} + +void EndConfiguration() { + configuring = false; +} + +} // end of namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h new file mode 100644 index 000000000..9b02d1382 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h @@ -0,0 +1,116 @@ +#pragma once +#include <algorithm> +#include <libusb.h> +#include <mutex> +#include <functional> +#include "common/common_types.h" + + +enum { + PAD_USE_ORIGIN = 0x0080, + PAD_GET_ORIGIN = 0x2000, + PAD_ERR_STATUS = 0x8000, +}; + +enum PadButton { + PAD_BUTTON_LEFT = 0x0001, + PAD_BUTTON_RIGHT = 0x0002, + PAD_BUTTON_DOWN = 0x0004, + PAD_BUTTON_UP = 0x0008, + PAD_TRIGGER_Z = 0x0010, + PAD_TRIGGER_R = 0x0020, + PAD_TRIGGER_L = 0x0040, + PAD_BUTTON_A = 0x0100, + PAD_BUTTON_B = 0x0200, + PAD_BUTTON_X = 0x0400, + PAD_BUTTON_Y = 0x0800, + PAD_BUTTON_START = 0x1000, + // Below is for compatibility with "AxisButton" type + PAD_STICK = 0x2000, + +}; + +enum PadAxes { STICK_X, STICK_Y, SUBSTICK_X, SUBSTICK_Y, TRIGGER_LEFT, TRIGGER_RIGHT }; + +struct GCPadStatus { + u16 button; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + u8 stickX; // 0 <= stickX <= 255 + u8 stickY; // 0 <= stickY <= 255 + u8 substickX; // 0 <= substickX <= 255 + u8 substickY; // 0 <= substickY <= 255 + u8 triggerLeft; // 0 <= triggerLeft <= 255 + u8 triggerRight; // 0 <= triggerRight <= 255 + bool isConnected{true}; + + static const u8 MAIN_STICK_CENTER_X = 0x80; + static const u8 MAIN_STICK_CENTER_Y = 0x80; + static const u8 MAIN_STICK_RADIUS = 0x7f; + static const u8 C_STICK_CENTER_X = 0x80; + static const u8 C_STICK_CENTER_Y = 0x80; + static const u8 C_STICK_RADIUS = 0x7f; + + static const u8 TRIGGER_CENTER = 20; + static const u8 THRESHOLD = 10; + u8 port; + u8 axis_which = 255; + u8 axis_value = 255; +}; + +struct GCState { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, u16> axes; +}; + + +namespace GCAdapter { +enum ControllerTypes { + CONTROLLER_NONE = 0, + CONTROLLER_WIRED = 1, + CONTROLLER_WIRELESS = 2 +}; + +enum { + NO_ADAPTER_DETECTED = 0, + ADAPTER_DETECTED = 1, +}; + +// Current adapter status: detected/not detected/in error (holds the error code) +static int current_status = NO_ADAPTER_DETECTED; + +GCPadStatus CheckStatus(int port, u8 adapter_payload[37]); +/// Initialize the GC Adapter capture and read sequence +void Init(); + +/// Close the adapter read thread and release the adapter +void Shutdown(); + +/// Begin scanning for the GC Adapter. +void StartScanThread(); + +/// Stop scanning for the adapter +void StopScanThread(); + +/// Returns true if there is a device connected to port +bool DeviceConnected(int port); + +/// Resets status of device connected to port +void ResetDeviceType(int port); + +/// Returns true if we successfully gain access to GC Adapter +bool CheckDeviceAccess(libusb_device* device); + +/// Captures GC Adapter endpoint address, +void GetGCEndpoint(libusb_device* device); + +/// For shutting down, clear all data, join all threads, release usb +void Reset(); + +/// For use in initialization, querying devices to find the adapter +void Setup(); + +/// Used for polling +void BeginConfiguration(); + +void EndConfiguration(); + +} // end of namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp new file mode 100644 index 000000000..772bd8890 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -0,0 +1,310 @@ +#include <atomic> +#include <list> +#include <mutex> +#include <utility> +#include "input_common/gcadapter/gc_poller.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "common/threadsafe_queue.h" + +// Using extern as to avoid multply defined symbols. +extern Common::SPSCQueue<GCPadStatus> pad_queue[4]; +extern struct GCState state[4]; + +namespace InputCommon { + +class GCButton final : public Input::ButtonDevice { +public: + explicit GCButton(int port_, int button_, int axis_) + : port(port_), button(button_) { + } + + ~GCButton() override; + + bool GetStatus() const override { + return state[port].buttons.at(button); + } + +private: + const int port; + const int button; +}; + +class GCAxisButton final : public Input::ButtonDevice { +public: + explicit GCAxisButton(int port_, int axis_, float threshold_, + bool trigger_if_greater_) + : port(port_), axis(axis_), threshold(threshold_), + trigger_if_greater(trigger_if_greater_) { + } + + + bool GetStatus() const override { + const float axis_value = (state[port].axes.at(axis) - 128.0f) / 128.0f; + if (trigger_if_greater) { + return axis_value > 0.10f; //TODO(ameerj) : Fix threshold. + } + return axis_value < -0.10f; + } + +private: + const int port; + const int axis; + float threshold; + bool trigger_if_greater; +}; + +GCButtonFactory::GCButtonFactory() { + GCAdapter::Init(); +} + +GCButton::~GCButton() { + GCAdapter::Shutdown(); +} + +std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { + int button_id = params.Get("button", 0); + int port = params.Get("port", 0); + // For Axis buttons, used by the binary sticks. + 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); + } + return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater); + } + + std::unique_ptr<GCButton> button = + std::make_unique<GCButton>(port, button_id, params.Get("axis", 0)); + return std::move(button); +} + +Common::ParamPackage GCButtonFactory::GetNextInput() { + Common::ParamPackage params; + GCPadStatus pad; + for (int i = 0; i < 4; i++) { + while (pad_queue[i].Pop(pad)) { + // This while loop will break on the earliest detected button + params.Set("engine", "gcpad"); + params.Set("port", i); + // I was debating whether to keep these verbose for ease of reading + // or to use a while loop shifting the bits to test and set the value. + if (pad.button & PAD_BUTTON_A) { + params.Set("button", PAD_BUTTON_A); + break; + } + if (pad.button & PAD_BUTTON_B) { + params.Set("button", PAD_BUTTON_B); + break; + } + if (pad.button & PAD_BUTTON_X) { + params.Set("button", PAD_BUTTON_X); + break; + } + if (pad.button & PAD_BUTTON_Y) { + params.Set("button", PAD_BUTTON_Y); + break; + } + if (pad.button & PAD_BUTTON_DOWN) { + params.Set("button", PAD_BUTTON_DOWN); + break; + } + if (pad.button & PAD_BUTTON_LEFT) { + params.Set("button", PAD_BUTTON_LEFT); + break; + } + if (pad.button & PAD_BUTTON_RIGHT) { + params.Set("button", PAD_BUTTON_RIGHT); + break; + } + if (pad.button & PAD_BUTTON_UP) { + params.Set("button", PAD_BUTTON_UP); + break; + } + if (pad.button & PAD_TRIGGER_L) { + params.Set("button", PAD_TRIGGER_L); + break; + } + if (pad.button & PAD_TRIGGER_R) { + params.Set("button", PAD_TRIGGER_R); + break; + } + if (pad.button & PAD_TRIGGER_Z) { + params.Set("button", PAD_TRIGGER_Z); + break; + } + if (pad.button & PAD_BUTTON_START) { + params.Set("button", PAD_BUTTON_START); + break; + } + // For Axis button implementation + if (pad.axis_which != 255) { + params.Set("axis", pad.axis_which); + params.Set("button", PAD_STICK); + if (pad.axis_value > 128) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.5"); + } + break; + } + } + } + return params; +} + +void GCButtonFactory::BeginConfiguration() { + polling = true; + for (int i = 0; i < 4; i++) + pad_queue[i].Clear(); + GCAdapter::BeginConfiguration(); +} + +void GCButtonFactory::EndConfiguration() { + polling = false; + + for (int i = 0; i < 4; i++) + pad_queue[i].Clear(); + GCAdapter::EndConfiguration(); +} + +class GCAnalog final : public Input::AnalogDevice { +public: + GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_) + : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) { + } + + float GetAxis(int axis) const { + std::lock_guard lock{mutex}; + // 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 (state[port].axes.at(axis) - 128.0f) / 95.0f; + } + + std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { + float x = GetAxis(axis_x); + float y = GetAxis(axis_y); + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return std::make_tuple(x, y); + } + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = GetAnalog(axis_x, axis_y); + 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); + } + + bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { + const auto [x, y] = GetStatus(); + const float directional_deadzone = 0.4f; + switch (direction) { + case Input::AnalogDirection::RIGHT: + return x > directional_deadzone; + case Input::AnalogDirection::LEFT: + return x < -directional_deadzone; + case Input::AnalogDirection::UP: + return y > directional_deadzone; + case Input::AnalogDirection::DOWN: + return y < -directional_deadzone; + } + return false; + } + +private: + const int port; + const int axis_x; + const int axis_y; + const float deadzone; + mutable std::mutex mutex; +}; + + +/// An analog device factory that creates analog devices from GC Adapter +GCAnalogFactory::GCAnalogFactory() {}; + + +/** +* Creates analog device from joystick axes +* @param params contains parameters for creating the device: +* - "port": the nth gcpad on the adapter +* - "axis_x": the index of the axis to be bind as x-axis +* - "axis_y": the index of the axis to be bind as y-axis +*/ +std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { + const std::string guid = params.Get("guid", "0"); + 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); + + return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone); +} + +void GCAnalogFactory::BeginConfiguration() { + polling = true; + for (int i = 0; i < 4; i++) + pad_queue[i].Clear(); + GCAdapter::BeginConfiguration(); +} + +void GCAnalogFactory::EndConfiguration() { + polling = false; + for (int i = 0; i < 4; i++) + pad_queue[i].Clear(); + GCAdapter::EndConfiguration(); +} + +Common::ParamPackage GCAnalogFactory::GetNextInput() { + GCPadStatus pad; + for (int i = 0; i < 4; i++) { + while (pad_queue[i].Pop(pad)) { + if (pad.axis_which == 255 || std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { + 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 = pad.axis_which; + if (analog_x_axis == -1) { + analog_x_axis = axis; + controller_number = i; + } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == i) { + analog_y_axis = axis; + } + } + } + Common::ParamPackage params; + if (analog_x_axis != -1 && analog_y_axis != -1) { + params.Set("engine", "gcpad"); + params.Set("port", controller_number); + params.Set("axis_x", analog_x_axis); + params.Set("axis_y", analog_y_axis); + analog_x_axis = -1; + analog_y_axis = -1; + controller_number = -1; + return params; + } + return params; +} +} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h new file mode 100644 index 000000000..d115b1d2a --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h @@ -0,0 +1,59 @@ +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + + +/** + * A button device factory representing a gcpad. It receives gcpad events and forward them + * to all button devices it created. + */ +class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + GCButtonFactory(); + + /** + * Creates a button device from a button press + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() { + return polling; + } + +private: + bool polling = false; +}; + +/// An analog device factory that creates analog devices from GC Adapter +class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + GCAnalogFactory(); + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() { + return polling; + } + +private: + int analog_x_axis = -1; + int analog_y_axis = -1; + int controller_number = -1; + bool polling = false; +}; +} // namespace InputCommon |