From 5333db91c1433e27d2b38506e76c7b9372b91b68 Mon Sep 17 00:00:00 2001 From: german Date: Mon, 12 Oct 2020 18:11:22 -0500 Subject: [PATCH] Add hotplug, rumble and fix 3rd party adapters for the GC adapter --- src/input_common/gcadapter/gc_adapter.cpp | 470 +++++++++++++--------- src/input_common/gcadapter/gc_adapter.h | 152 ++++--- src/input_common/gcadapter/gc_poller.cpp | 128 +++--- 3 files changed, 440 insertions(+), 310 deletions(-) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index c95feb0d7..b912188b6 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -21,26 +21,6 @@ namespace GCAdapter { -// Used to loop through and assign button in poller -constexpr std::array PadButtonArray{ - PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, - PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R, - PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, - PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START, -}; - -static void PadToState(const GCPadStatus& pad, GCState& out_state) { - for (const auto& button : PadButtonArray) { - const auto button_key = static_cast(button); - const auto button_value = (pad.button & button_key) != 0; - out_state.buttons.insert_or_assign(static_cast(button_key), button_value); - } - - for (std::size_t i = 0; i < pad.axis_values.size(); ++i) { - out_state.axes.insert_or_assign(static_cast(i), pad.axis_values[i]); - } -} - Adapter::Adapter() { if (usb_adapter_handle != nullptr) { return; @@ -49,168 +29,263 @@ Adapter::Adapter() { const int init_res = libusb_init(&libusb_ctx); if (init_res == LIBUSB_SUCCESS) { - Setup(); + adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); } else { LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); } } -GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array& adapter_payload) { - GCPadStatus pad = {}; - const std::size_t offset = 1 + (9 * port); +Adapter::~Adapter() { + Reset(); +} - adapter_controllers_status[port] = static_cast(adapter_payload[offset] >> 4); +void Adapter::AdapterInputThread() { + LOG_DEBUG(Input, "GC Adapter input thread started"); + s32 payload_size{}; + AdapterPayload adapter_payload{}; + + if (adapter_scan_thread.joinable()) { + adapter_scan_thread.join(); + } + + while (adapter_input_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + static_cast(adapter_payload.size()), &payload_size, 16); + if (IsPayloadCorrect(adapter_payload, payload_size)) { + UpdateControllers(adapter_payload); + UpdateVibrations(); + } + std::this_thread::yield(); + } + + if (restart_scan_thread) { + adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + restart_scan_thread = false; + } +} + +bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { + if (payload_size != static_cast(adapter_payload.size()) || + adapter_payload[0] != LIBUSB_DT_HID) { + LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, + adapter_payload[0]); + if (input_error_counter++ > 20) { + LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); + adapter_input_thread_running = false; + restart_scan_thread = true; + } + return false; + } + + input_error_counter = 0; + return true; +} + +void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { + for (std::size_t port = 0; port < pads.size(); ++port) { + const std::size_t offset = 1 + (9 * port); + const auto type = static_cast(adapter_payload[offset] >> 4); + UpdatePadType(port, type); + if (DeviceConnected(port)) { + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; + UpdateStateButtons(port, b1, b2); + UpdateStateAxes(port, adapter_payload); + if (configuring) { + UpdateYuzuSettings(port); + } + } + } +} + +void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { + if (pads[port].type == pad_type) { + return; + } + // Device changed reset device and set new type + ResetDevice(port); + pads[port].type = pad_type; +} + +void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { + if (port >= pads.size()) { + return; + } static constexpr std::array b1_buttons{ - PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, - PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, - PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, + PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, + PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, }; static constexpr std::array b2_buttons{ - PadButton::PAD_BUTTON_START, - PadButton::PAD_TRIGGER_Z, - PadButton::PAD_TRIGGER_R, - PadButton::PAD_TRIGGER_L, + PadButton::ButtonStart, + PadButton::TriggerZ, + PadButton::TriggerR, + PadButton::TriggerL, }; + pads[port].buttons = 0; + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + if ((b1 & (1U << i)) != 0) { + pads[port].buttons = + static_cast(pads[port].buttons | static_cast(b1_buttons[i])); + pads[port].last_button = b1_buttons[i]; + } + } + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + if ((b2 & (1U << j)) != 0) { + pads[port].buttons = + static_cast(pads[port].buttons | static_cast(b2_buttons[j])); + pads[port].last_button = b2_buttons[j]; + } + } +} + +void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { + if (port >= pads.size()) { + return; + } + + const std::size_t offset = 1 + (9 * port); static constexpr std::array 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; + for (const PadAxes axis : axes) { + const auto index = static_cast(axis); + const u8 axis_value = adapter_payload[offset + 3 + index]; + if (pads[port].axis_origin[index] == 255) { + pads[port].axis_origin[index] = axis_value; + } + pads[port].axis_values[index] = + static_cast(axis_value - pads[port].axis_origin[index]); } - - if (adapter_controllers_status[port] != ControllerTypes::None) { - 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) { - pad.button = static_cast(pad.button | static_cast(b1_buttons[i])); - } - } - - for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pad.button = static_cast(pad.button | static_cast(b2_buttons[j])); - } - } - for (PadAxes axis : axes) { - const auto index = static_cast(axis); - pad.axis_values[index] = adapter_payload[offset + 3 + index]; - } - - if (get_origin[port]) { - origin_status[port].axis_values = pad.axis_values; - get_origin[port] = false; - } - } - return pad; } -void Adapter::Read() { - LOG_DEBUG(Input, "GC Adapter Read() thread started"); +void Adapter::UpdateYuzuSettings(std::size_t port) { + if (port >= pads.size()) { + return; + } - int payload_size; - std::array adapter_payload; - std::array pads; + constexpr u8 axis_threshold = 50; + GCPadStatus pad_status = {.port = port}; - while (adapter_thread_running) { - libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), - sizeof(adapter_payload), &payload_size, 16); + if (pads[port].buttons != 0) { + pad_status.button = pads[port].last_button; + pad_queue.Push(pad_status); + } - 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; + // Accounting for a threshold here to ensure an intentional press + for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { + const s16 value = pads[port].axis_values[i]; + + if (value > axis_threshold || value < -axis_threshold) { + pad_status.axis = static_cast(i); + pad_status.axis_value = value; + pad_status.axis_threshold = axis_threshold; + pad_queue.Push(pad_status); } - for (std::size_t port = 0; port < pads.size(); ++port) { - 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 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]; +void Adapter::UpdateVibrations() { + // Use 8 states to keep the switching between on/off fast enough for + // a human to not notice the difference between switching from on/off + // More states = more rumble strengths = slower update time + constexpr u8 vibration_states = 8; - if (value > origin + pads[port].THRESHOLD || - value < origin - pads[port].THRESHOLD) { - pads[port].axis = static_cast(i); - pads[port].axis_value = pads[port].axis_values[i]; - pad_queue[port].Push(pads[port]); - } - } - } - PadToState(pads[port], state[port]); + vibration_counter = (vibration_counter + 1) % vibration_states; + + for (GCController& pad : pads) { + const bool vibrate = pad.rumble_amplitude > vibration_counter; + vibration_changed |= vibrate != pad.enable_vibration; + pad.enable_vibration = vibrate; + } + SendVibrations(); +} + +void Adapter::SendVibrations() { + if (!rumble_enabled || !vibration_changed) { + return; + } + s32 size{}; + constexpr u8 rumble_command = 0x11; + const u8 p1 = pads[0].enable_vibration; + const u8 p2 = pads[1].enable_vibration; + const u8 p3 = pads[2].enable_vibration; + const u8 p4 = pads[3].enable_vibration; + std::array payload = {rumble_command, p1, p2, p3, p4}; + const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(), + static_cast(payload.size()), &size, 16); + if (err) { + LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); + if (output_error_counter++ > 5) { + LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); + rumble_enabled = false; } - std::this_thread::yield(); + return; + } + output_error_counter = 0; + vibration_changed = false; +} + +bool Adapter::RumblePlay(std::size_t port, f32 amplitude) { + amplitude = std::clamp(amplitude, 0.0f, 1.0f); + const auto raw_amp = static_cast(amplitude * 0x8); + pads[port].rumble_amplitude = raw_amp; + + return rumble_enabled; +} + +void Adapter::AdapterScanThread() { + adapter_scan_thread_running = true; + adapter_input_thread_running = false; + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } + ClearLibusbHandle(); + ResetDevices(); + while (adapter_scan_thread_running && !adapter_input_thread_running) { + Setup(); + std::this_thread::sleep_for(std::chrono::seconds(1)); } } void Adapter::Setup() { - // Initialize all controllers as unplugged - adapter_controllers_status.fill(ControllerTypes::None); - // Initialize all ports to store axis origin values - get_origin.fill(true); + usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); - // pointer to list of connected usb devices - libusb_device** devices{}; - - // populate the list of devices, get the count - 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); + if (usb_adapter_handle == NULL) { + return; + } + if (!CheckDeviceAccess()) { + ClearLibusbHandle(); return; } - if (devices != nullptr) { - for (std::size_t index = 0; index < static_cast(device_count); ++index) { - if (CheckDeviceAccess(devices[index])) { - // GC Adapter found and accessible, registering it - GetGCEndpoint(devices[index]); - break; - } - } - libusb_free_device_list(devices, 1); + libusb_device* device = libusb_get_device(usb_adapter_handle); + + LOG_INFO(Input, "GC adapter is now connected"); + // GC Adapter found and accessible, registering it + if (GetGCEndpoint(device)) { + adapter_scan_thread_running = false; + adapter_input_thread_running = true; + rumble_enabled = true; + input_error_counter = 0; + output_error_counter = 0; + adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); } } -bool Adapter::CheckDeviceAccess(libusb_device* device) { - libusb_device_descriptor desc; - const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); - if (get_descriptor_error) { - // could not acquire the descriptor, no point in trying to use it. - LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", - get_descriptor_error); - return false; +bool Adapter::CheckDeviceAccess() { + // This fixes payload problems from offbrand GCAdapters + const s32 control_transfer_error = + libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); + if (control_transfer_error < 0) { + LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); } - if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { - // This isn't the device we are looking for. - return false; - } - const int open_error = libusb_open(device, &usb_adapter_handle); - - if (open_error == LIBUSB_ERROR_ACCESS) { - LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", - desc.idVendor, desc.idProduct); - return false; - } - if (open_error) { - LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); - return false; - } - - int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); + s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); if (kernel_driver_error == 1) { kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { @@ -236,13 +311,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) { return true; } -void Adapter::GetGCEndpoint(libusb_device* device) { +bool Adapter::GetGCEndpoint(libusb_device* device) { libusb_config_descriptor* config = nullptr; const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); if (config_descriptor_return != LIBUSB_SUCCESS) { LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", config_descriptor_return); - return; + return false; } for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { @@ -264,31 +339,51 @@ void Adapter::GetGCEndpoint(libusb_device* device) { unsigned char clear_payload = 0x13; libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, sizeof(clear_payload), nullptr, 16); - - adapter_thread_running = true; - adapter_input_thread = std::thread(&Adapter::Read, this); + return true; } -Adapter::~Adapter() { - Reset(); -} +void Adapter::JoinThreads() { + restart_scan_thread = false; + adapter_input_thread_running = false; + adapter_scan_thread_running = false; -void Adapter::Reset() { - if (adapter_thread_running) { - adapter_thread_running = false; + if (adapter_scan_thread.joinable()) { + adapter_scan_thread.join(); } + if (adapter_input_thread.joinable()) { adapter_input_thread.join(); } +} - adapter_controllers_status.fill(ControllerTypes::None); - get_origin.fill(true); - +void Adapter::ClearLibusbHandle() { if (usb_adapter_handle) { libusb_release_interface(usb_adapter_handle, 1); libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; } +} + +void Adapter::ResetDevices() { + for (std::size_t i = 0; i < pads.size(); ++i) { + ResetDevice(i); + } +} + +void Adapter::ResetDevice(std::size_t port) { + pads[port].type = ControllerTypes::None; + pads[port].enable_vibration = false; + pads[port].rumble_amplitude = 0; + pads[port].buttons = 0; + pads[port].last_button = PadButton::Undefined; + pads[port].axis_values.fill(0); + pads[port].axis_origin.fill(255); +} + +void Adapter::Reset() { + JoinThreads(); + ClearLibusbHandle(); + ResetDevices(); if (libusb_ctx) { libusb_exit(libusb_ctx); @@ -297,11 +392,11 @@ void Adapter::Reset() { std::vector Adapter::GetInputDevices() const { std::vector devices; - for (std::size_t port = 0; port < state.size(); ++port) { + for (std::size_t port = 0; port < pads.size(); ++port) { if (!DeviceConnected(port)) { continue; } - std::string name = fmt::format("Gamecube Controller {}", port); + std::string name = fmt::format("Gamecube Controller {}", port + 1); devices.emplace_back(Common::ParamPackage{ {"class", "gcpad"}, {"display", std::move(name)}, @@ -318,18 +413,18 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( // This list also excludes any button that can't be really mapped static constexpr std::array, 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}, + std::pair{Settings::NativeButton::A, PadButton::ButtonA}, + {Settings::NativeButton::B, PadButton::ButtonB}, + {Settings::NativeButton::X, PadButton::ButtonX}, + {Settings::NativeButton::Y, PadButton::ButtonY}, + {Settings::NativeButton::Plus, PadButton::ButtonStart}, + {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, + {Settings::NativeButton::DUp, PadButton::ButtonUp}, + {Settings::NativeButton::DRight, PadButton::ButtonRight}, + {Settings::NativeButton::DDown, PadButton::ButtonDown}, + {Settings::NativeButton::SL, PadButton::TriggerL}, + {Settings::NativeButton::SR, PadButton::TriggerR}, + {Settings::NativeButton::R, PadButton::TriggerZ}, }; if (!params.Has("port")) { return {}; @@ -352,8 +447,10 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( 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(PadButton::PAD_STICK)); - button_params.Set("axis", static_cast(gcadapter_axis)); + button_params.Set("button", static_cast(PadButton::Stick)); + button_params.Set("axis", static_cast(gcadapter_axis)); + button_params.Set("threshold", 0.5f); + button_params.Set("direction", "+"); mapping.insert_or_assign(switch_button, std::move(button_params)); } return mapping; @@ -382,46 +479,33 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( } bool Adapter::DeviceConnected(std::size_t port) const { - return adapter_controllers_status[port] != ControllerTypes::None; -} - -void Adapter::ResetDeviceType(std::size_t port) { - adapter_controllers_status[port] = ControllerTypes::None; + return pads[port].type != ControllerTypes::None; } void Adapter::BeginConfiguration() { - get_origin.fill(true); - for (auto& pq : pad_queue) { - pq.Clear(); - } + pad_queue.Clear(); configuring = true; } void Adapter::EndConfiguration() { - for (auto& pq : pad_queue) { - pq.Clear(); - } + pad_queue.Clear(); configuring = false; } -std::array, 4>& Adapter::GetPadQueue() { +Common::SPSCQueue& Adapter::GetPadQueue() { return pad_queue; } -const std::array, 4>& Adapter::GetPadQueue() const { +const Common::SPSCQueue& Adapter::GetPadQueue() const { return pad_queue; } -std::array& Adapter::GetPadState() { - return state; +GCController& Adapter::GetPadState(std::size_t port) { + return pads.at(port); } -const std::array& Adapter::GetPadState() const { - return state; -} - -int Adapter::GetOriginValue(u32 port, u32 axis) const { - return origin_status[port].axis_values[axis]; +const GCController& Adapter::GetPadState(std::size_t port) const { + return pads.at(port); } } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index 4f5f3de8e..d28dcfad3 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -19,24 +19,23 @@ struct libusb_device_handle; namespace GCAdapter { enum class 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, + Undefined = 0x0000, + ButtonLeft = 0x0001, + ButtonRight = 0x0002, + ButtonDown = 0x0004, + ButtonUp = 0x0008, + TriggerZ = 0x0010, + TriggerR = 0x0020, + TriggerL = 0x0040, + ButtonA = 0x0100, + ButtonB = 0x0200, + ButtonX = 0x0400, + ButtonY = 0x0800, + ButtonStart = 0x1000, // Below is for compatibility with "AxisButton" type - PAD_STICK = 0x2000, + Stick = 0x2000, }; -extern const std::array PadButtonArray; - enum class PadAxes : u8 { StickX, StickY, @@ -47,87 +46,122 @@ enum class PadAxes : u8 { Undefined, }; +enum class ControllerTypes { + None, + Wired, + Wireless, +}; + struct GCPadStatus { - u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + std::size_t port{}; - std::array axis_values{}; // Triggers and sticks, following indices defined in PadAxes - static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling + PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - u8 port{}; PadAxes axis{PadAxes::Undefined}; - u8 axis_value{255}; + s16 axis_value{}; + u8 axis_threshold{50}; }; -struct GCState { - std::unordered_map buttons; - std::unordered_map axes; +struct GCController { + ControllerTypes type{}; + bool enable_vibration{}; + u8 rumble_amplitude{}; + u16 buttons{}; + PadButton last_button{}; + std::array axis_values{}; + std::array axis_origin{}; }; -enum class ControllerTypes { None, Wired, Wireless }; - class Adapter { public: - /// Initialize the GC Adapter capture and read sequence Adapter(); - - /// Close the adapter read thread and release the adapter ~Adapter(); + + /// Request a vibration for a controlelr + bool RumblePlay(std::size_t port, f32 amplitude); + /// Used for polling void BeginConfiguration(); void EndConfiguration(); - std::vector GetInputDevices() const; - InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; - InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; + Common::SPSCQueue& GetPadQueue(); + const Common::SPSCQueue& GetPadQueue() const; + + GCController& GetPadState(std::size_t port); + const GCController& GetPadState(std::size_t port) const; /// Returns true if there is a device connected to port bool DeviceConnected(std::size_t port) const; - std::array, 4>& GetPadQueue(); - const std::array, 4>& GetPadQueue() const; - - std::array& GetPadState(); - const std::array& GetPadState() const; - - int GetOriginValue(u32 port, u32 axis) const; + /// Used for automapping features + std::vector GetInputDevices() const; + InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; private: - GCPadStatus GetPadStatus(std::size_t port, const std::array& adapter_payload); + using AdapterPayload = std::array; - void Read(); + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateYuzuSettings(std::size_t port); + void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); + void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); + void UpdateVibrations(); - /// Resets status of device connected to port - void ResetDeviceType(std::size_t port); + void AdapterInputThread(); - /// Returns true if we successfully gain access to GC Adapter - bool CheckDeviceAccess(libusb_device* device); + void AdapterScanThread(); - /// Captures GC Adapter endpoint address, - void GetGCEndpoint(libusb_device* device); + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); - /// For shutting down, clear all data, join all threads, release usb - void Reset(); + // Updates vibration state of all controllers + void SendVibrations(); /// For use in initialization, querying devices to find the adapter void Setup(); + /// Resets status of all GC controller devices to a disconected state + void ResetDevices(); + + /// Resets status of device connected to a disconected state + void ResetDevice(std::size_t port); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(); + + /// Captures GC Adapter endpoint address + /// Returns true if the endpoind was set correctly + bool GetGCEndpoint(libusb_device* device); + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + // Join all threads + void JoinThreads(); + + // Release usb handles + void ClearLibusbHandle(); + libusb_device_handle* usb_adapter_handle = nullptr; + std::array pads; + Common::SPSCQueue pad_queue; std::thread adapter_input_thread; - bool adapter_thread_running; + std::thread adapter_scan_thread; + bool adapter_input_thread_running; + bool adapter_scan_thread_running; + bool restart_scan_thread; libusb_context* libusb_ctx; - u8 input_endpoint = 0; - u8 output_endpoint = 0; + u8 input_endpoint{0}; + u8 output_endpoint{0}; + u8 input_error_counter{0}; + u8 output_error_counter{0}; + int vibration_counter{0}; - bool configuring = false; - - std::array state; - std::array get_origin; - std::array origin_status; - std::array, 4> pad_queue; - std::array adapter_controllers_status{}; + bool configuring{false}; + bool rumble_enabled{true}; + bool vibration_changed{true}; }; - } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 893556916..6bd6f57fc 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -15,22 +15,30 @@ namespace InputCommon { class GCButton final : public Input::ButtonDevice { public: - explicit GCButton(u32 port_, int button_, const GCAdapter::Adapter* adapter) + explicit GCButton(u32 port_, s32 button_, GCAdapter::Adapter* adapter) : port(port_), button(button_), gcadapter(adapter) {} ~GCButton() override; bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { - return gcadapter->GetPadState()[port].buttons.at(button); + return (gcadapter->GetPadState(port).buttons & button) != 0; } return false; } + bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override { + const float amplitude = amp_high + amp_low > 2.0f ? 1.0f : (amp_high + amp_low) * 0.5f; + const auto new_amp = + static_cast(pow(amplitude, 0.5f) * (3.0f - 2.0f * pow(amplitude, 0.15f))); + + return gcadapter->RumblePlay(port, new_amp); + } + private: const u32 port; - const int button; - const GCAdapter::Adapter* gcadapter; + const s32 button; + GCAdapter::Adapter* gcadapter; }; class GCAxisButton final : public Input::ButtonDevice { @@ -38,13 +46,12 @@ public: explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, const GCAdapter::Adapter* adapter) : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), - gcadapter(adapter), - origin_value(static_cast(adapter->GetOriginValue(port_, axis_))) {} + gcadapter(adapter) {} bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { - const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis); - const float axis_value = (current_axis_value - origin_value) / 128.0f; + const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); + const float axis_value = current_axis_value / 128.0f; if (trigger_if_greater) { // TODO: Might be worthwile to set a slider for the trigger threshold. It is // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick @@ -61,7 +68,6 @@ private: float threshold; bool trigger_if_greater; const GCAdapter::Adapter* gcadapter; - const float origin_value; }; GCButtonFactory::GCButtonFactory(std::shared_ptr adapter_) @@ -73,7 +79,7 @@ std::unique_ptr GCButtonFactory::Create(const Common::Param const auto button_id = params.Get("button", 0); const auto port = static_cast(params.Get("port", 0)); - constexpr int PAD_STICK_ID = static_cast(GCAdapter::PadButton::PAD_STICK); + constexpr s32 PAD_STICK_ID = static_cast(GCAdapter::PadButton::Stick); // button is not an axis/stick button if (button_id != PAD_STICK_ID) { @@ -106,32 +112,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() const { Common::ParamPackage params; GCAdapter::GCPadStatus pad; auto& queue = adapter->GetPadQueue(); - for (std::size_t port = 0; port < queue.size(); ++port) { - while (queue[port].Pop(pad)) { - // This while loop will break on the earliest detected button - params.Set("engine", "gcpad"); - params.Set("port", static_cast(port)); - for (const auto& button : GCAdapter::PadButtonArray) { - const u16 button_value = static_cast(button); - if (pad.button & button_value) { - params.Set("button", button_value); - break; - } - } + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + params.Set("engine", "gcpad"); + params.Set("port", static_cast(pad.port)); + if (pad.button != GCAdapter::PadButton::Undefined) { + params.Set("button", static_cast(pad.button)); + } - // For Axis button implementation - if (pad.axis != GCAdapter::PadAxes::Undefined) { - params.Set("axis", static_cast(pad.axis)); - params.Set("button", static_cast(GCAdapter::PadButton::PAD_STICK)); - if (pad.axis_value > 128) { - params.Set("direction", "+"); - params.Set("threshold", "0.25"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.25"); - } - break; + // For Axis button implementation + if (pad.axis != GCAdapter::PadAxes::Undefined) { + params.Set("axis", static_cast(pad.axis)); + params.Set("button", static_cast(GCAdapter::PadButton::Stick)); + params.Set("threshold", "0.25"); + if (pad.axis_value > 0) { + params.Set("direction", "+"); + } else { + params.Set("direction", "-"); } + break; } } return params; @@ -152,17 +151,14 @@ public: explicit GCAnalog(u32 port_, u32 axis_x_, u32 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(static_cast(adapter->GetOriginValue(port_, axis_x_))), - origin_value_y(static_cast(adapter->GetOriginValue(port_, axis_y_))), range(range_) {} float GetAxis(u32 axis) const { if (gcadapter->DeviceConnected(port)) { std::lock_guard lock{mutex}; - const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; const auto axis_value = - static_cast(gcadapter->GetPadState()[port].axes.at(axis)); - return (axis_value - origin_value) / (100.0f * range); + static_cast(gcadapter->GetPadState(port).axis_values.at(axis)); + return (axis_value) / (100.0f * range); } return 0.0f; } @@ -215,8 +211,6 @@ private: const u32 axis_y; const float deadzone; const GCAdapter::Adapter* gcadapter; - const float origin_value_x; - const float origin_value_y; const float range; mutable std::mutex mutex; }; @@ -254,26 +248,44 @@ void GCAnalogFactory::EndConfiguration() { Common::ParamPackage GCAnalogFactory::GetNextInput() { GCAdapter::GCPadStatus pad; + Common::ParamPackage params; auto& queue = adapter->GetPadQueue(); - for (std::size_t port = 0; port < queue.size(); ++port) { - while (queue[port].Pop(pad)) { - if (pad.axis == GCAdapter::PadAxes::Undefined || - std::abs((static_cast(pad.axis_value) - 128.0f) / 128.0f) < 0.1f) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second input event. The axes also must be from the same joystick. - const u8 axis = static_cast(pad.axis); - if (analog_x_axis == -1) { - analog_x_axis = axis; - controller_number = static_cast(port); - } else if (analog_y_axis == -1 && analog_x_axis != axis && - controller_number == static_cast(port)) { - analog_y_axis = axis; - } + while (queue.Pop(pad)) { + if (pad.button != GCAdapter::PadButton::Undefined) { + params.Set("engine", "gcpad"); + params.Set("port", static_cast(pad.port)); + params.Set("button", static_cast(pad.button)); + return params; + } + if (pad.axis == GCAdapter::PadAxes::Undefined || + std::abs(static_cast(pad.axis_value) / 128.0f) < 0.1f) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second input event. The axes also must be from the same joystick. + const u8 axis = static_cast(pad.axis); + if (axis == 0 || axis == 1) { + analog_x_axis = 0; + analog_y_axis = 1; + controller_number = static_cast(pad.port); + break; + } + if (axis == 2 || axis == 3) { + analog_x_axis = 2; + analog_y_axis = 3; + controller_number = static_cast(pad.port); + break; + } + + if (analog_x_axis == -1) { + analog_x_axis = axis; + controller_number = static_cast(pad.port); + } else if (analog_y_axis == -1 && analog_x_axis != axis && + controller_number == static_cast(pad.port)) { + analog_y_axis = axis; + break; } } - Common::ParamPackage params; if (analog_x_axis != -1 && analog_y_axis != -1) { params.Set("engine", "gcpad"); params.Set("port", controller_number);