From c5094ed6148f2fbb0be2f3772e38184c47ab8c4f Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 25 Aug 2020 22:21:32 -0400 Subject: [PATCH 1/8] externals: Track upstream libusb We can place the external in an inner folder and manage the custom files necessary to integrate it with CMake directly. This allows us to directly change how we use it with our build system, as opposed to needing to change a fork. --- .gitmodules | 3 + externals/libusb/CMakeLists.txt | 147 ++++++++++++++++++++++++++++++++ externals/libusb/config.h.in | 90 +++++++++++++++++++ externals/libusb/libusb | 1 + 4 files changed, 241 insertions(+) create mode 100644 externals/libusb/CMakeLists.txt create mode 100644 externals/libusb/config.h.in create mode 160000 externals/libusb/libusb diff --git a/.gitmodules b/.gitmodules index d4bc7837f..509f1722e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,6 +31,9 @@ [submodule "libressl"] path = externals/libressl url = https://github.com/citra-emu/ext-libressl-portable.git +[submodule "libusb"] + path = externals/libusb/libusb + url = https://github.com/libusb/libusb.git [submodule "cubeb"] path = externals/cubeb url = https://github.com/kinetiknz/cubeb.git diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt new file mode 100644 index 000000000..074ce01d3 --- /dev/null +++ b/externals/libusb/CMakeLists.txt @@ -0,0 +1,147 @@ +add_library(usb STATIC EXCLUDE_FROM_ALL + libusb/libusb/core.c + libusb/libusb/core.c + libusb/libusb/descriptor.c + libusb/libusb/hotplug.c + libusb/libusb/io.c + libusb/libusb/strerror.c + libusb/libusb/sync.c +) +set_target_properties(usb PROPERTIES VERSION 1.0.23) +if(WIN32) + target_include_directories(usb + BEFORE + PUBLIC + libusb/libusb + + PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}" + ) + + if (NOT MINGW) + target_include_directories(usb BEFORE PRIVATE libusb/msvc) + endif() +else() +target_include_directories(usb + # turns out other projects also have "config.h", so make sure the + # LibUSB one comes first + BEFORE + + PUBLIC + libusb/libusb + + PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}" +) +endif() + +if(WIN32 OR CYGWIN) + target_sources(usb PRIVATE + libusb/libusb/os/threads_windows.c + libusb/libusb/os/windows_winusb.c + libusb/libusb/os/windows_usbdk.c + libusb/libusb/os/windows_nt_common.c + ) + set(OS_WINDOWS TRUE) +elseif(APPLE) + target_sources(usb PRIVATE + libusb/libusb/os/darwin_usb.c + ) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + find_library(IOKIT_LIBRARY IOKit) + find_library(OBJC_LIBRARY objc) + target_link_libraries(usb PRIVATE + ${COREFOUNDATION_LIBRARY} + ${IOKIT_LIBRARY} + ${OBJC_LIBRARY} + ) + set(OS_DARWIN TRUE) +elseif(ANDROID) + target_sources(usb PRIVATE + libusb/libusb/os/linux_usbfs.c + libusb/libusb/os/linux_netlink.c + ) + find_library(LOG_LIBRARY log) + target_link_libraries(usb PRIVATE ${LOG_LIBRARY}) + set(OS_LINUX TRUE) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_sources(usb PRIVATE + libusb/libusb/os/linux_usbfs.c + ) + find_package(Libudev) + if(LIBUDEV_FOUND) + target_sources(usb PRIVATE + libusb/libusb/os/linux_udev.c + ) + target_link_libraries(usb PRIVATE "${LIBUDEV_LIBRARIES}") + target_include_directories(usb PRIVATE "${LIBUDEV_INCLUDE_DIR}") + set(HAVE_LIBUDEV TRUE) + set(USE_UDEV TRUE) + else() + target_sources(usb PRIVATE + libusb/libusb/os/linux_netlink.c + ) + endif() + set(OS_LINUX TRUE) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") + target_sources(usb PRIVATE + libusb/libusb/os/netbsd_usb.c + ) + set(OS_NETBSD TRUE) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") + target_sources(usb PRIVATE + libusb/libusb/os/openbsd_usb.c + ) + set(OS_OPENBSD TRUE) +endif() + +if(UNIX) + target_sources(usb PRIVATE + libusb/libusb/os/poll_posix.c + libusb/libusb/os/threads_posix.c + ) + find_package(Threads REQUIRED) + if(THREADS_HAVE_PTHREAD_ARG) + target_compile_options(usb PUBLIC "-pthread") + endif() + if(CMAKE_THREAD_LIBS_INIT) + target_link_libraries(usb PRIVATE "${CMAKE_THREAD_LIBS_INIT}") + endif() + set(THREADS_POSIX TRUE) +elseif(WIN32) + target_sources(usb PRIVATE + libusb/libusb/os/poll_windows.c + libusb/libusb/os/threads_windows.c + ) +endif() + +include(CheckFunctionExists) +include(CheckIncludeFiles) +include(CheckTypeSize) +check_include_files(asm/types.h HAVE_ASM_TYPES_H) +check_function_exists(gettimeofday HAVE_GETTIMEOFDAY) +check_include_files(linux/filter.h HAVE_LINUX_FILTER_H) +check_include_files(linux/netlink.h HAVE_LINUX_NETLINK_H) +check_include_files(poll.h HAVE_POLL_H) +check_include_files(signal.h HAVE_SIGNAL_H) +check_include_files(strings.h HAVE_STRINGS_H) +check_type_size("struct timespec" STRUCT_TIMESPEC) +check_function_exists(syslog HAVE_SYSLOG_FUNC) +check_include_files(syslog.h HAVE_SYSLOG_H) +check_include_files(sys/socket.h HAVE_SYS_SOCKET_H) +check_include_files(sys/time.h HAVE_SYS_TIME_H) +check_include_files(sys/types.h HAVE_SYS_TYPES_H) + +set(CMAKE_EXTRA_INCLUDE_FILES poll.h) +check_type_size("nfds_t" nfds_t) +unset(CMAKE_EXTRA_INCLUDE_FILES) +if(HAVE_NFDS_T) + set(POLL_NFDS_TYPE "nfds_t") +else() + set(POLL_NFDS_TYPE "unsigned int") +endif() + +check_include_files(sys/timerfd.h USBI_TIMERFD_AVAILABLE) + + +configure_file(config.h.in config.h) diff --git a/externals/libusb/config.h.in b/externals/libusb/config.h.in new file mode 100644 index 000000000..915b7390f --- /dev/null +++ b/externals/libusb/config.h.in @@ -0,0 +1,90 @@ +/* Default visibility */ +#if defined(__GNUC__) || defined(__clang__) + #define DEFAULT_VISIBILITY __attribute__((visibility("default"))) +#elif defined(_MSC_VER) + #define DEFAULT_VISIBILITY __declspec(dllexport) +#endif + +/* Start with debug message logging enabled */ +#undef ENABLE_DEBUG_LOGGING + +/* Message logging */ +#undef ENABLE_LOGGING + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ASM_TYPES_H 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#cmakedefine HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have the `udev' library (-ludev). */ +#cmakedefine HAVE_LIBUDEV 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_FILTER_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_NETLINK_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H 1 + +/* Define to 1 if the system has the type `struct timespec'. */ +#cmakedefine HAVE_STRUCT_TIMESPEC 1 + +/* syslog() function available */ +#cmakedefine HAVE_SYSLOG_FUNC 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H 1 + +/* Darwin backend */ +#cmakedefine OS_DARWIN 1 + +/* Linux backend */ +#cmakedefine OS_LINUX 1 + +/* NetBSD backend */ +#cmakedefine OS_NETBSD 1 + +/* OpenBSD backend */ +#cmakedefine OS_OPENBSD 1 + +/* Windows backend */ +#cmakedefine OS_WINDOWS 1 + +/* type of second poll() argument */ +#define POLL_NFDS_TYPE @POLL_NFDS_TYPE@ + +/* Use POSIX Threads */ +#cmakedefine THREADS_POSIX + +/* timerfd headers available */ +#cmakedefine USBI_TIMERFD_AVAILABLE 1 + +/* Enable output to system log */ +#define USE_SYSTEM_LOGGING_FACILITY 1 + +/* Use udev for device enumeration/hotplug */ +#cmakedefine USE_UDEV 1 + +/* Use GNU extensions */ +#define _GNU_SOURCE + +/* Oldest Windows version supported */ +#define WINVER 0x0501 diff --git a/externals/libusb/libusb b/externals/libusb/libusb new file mode 160000 index 000000000..e782eeb25 --- /dev/null +++ b/externals/libusb/libusb @@ -0,0 +1 @@ +Subproject commit e782eeb2514266f6738e242cdcb18e3ae1ed06fa From 05e28a53e80d257e60ebcedb033feba22d2a3f60 Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:03:50 -0500 Subject: [PATCH 2/8] input_common: Add support for GameCube Adapter This is a port of the initial GameCube adapter input support i added into yuzu emulator. It requires the same setup as when it was first introduced in yuzu, requiring the Zadig driver be installed for the adapter to allow it to interface with libusb. --- CMakeLists.txt | 11 + .../configuration/configure_input.cpp | 36 +- src/input_common/CMakeLists.txt | 5 + src/input_common/gcadapter/gc_adapter.cpp | 329 ++++++++++++++++++ src/input_common/gcadapter/gc_adapter.h | 132 +++++++ src/input_common/gcadapter/gc_poller.cpp | 272 +++++++++++++++ src/input_common/gcadapter/gc_poller.h | 67 ++++ src/input_common/main.cpp | 24 ++ 8 files changed, 875 insertions(+), 1 deletion(-) create mode 100644 src/input_common/gcadapter/gc_adapter.cpp create mode 100644 src/input_common/gcadapter/gc_adapter.h create mode 100644 src/input_common/gcadapter/gc_poller.cpp create mode 100644 src/input_common/gcadapter/gc_poller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fbbb0497..af8caa7fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,6 +193,17 @@ if (ENABLE_QT) endif() endif() +# Ensure libusb is properly configured (based on dolphin libusb include) +if(NOT APPLE) + include(FindPkgConfig) + find_package(LibUSB) +endif() +if (NOT LIBUSB_FOUND) + add_subdirectory(externals/libusb) + set(LIBUSB_INCLUDE_DIR "") + set(LIBUSB_LIBRARIES usb) +endif() + if (ENABLE_FFMPEG) if (CITRA_USE_BUNDLED_FFMPEG) if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64) diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 64fb7c840..6eb960c26 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -85,6 +85,20 @@ static QString ButtonToText(const Common::ParamPackage& param) { return {}; } + if (param.Get("engine", "") == "gcpad") { + if (param.Has("axis")) { + const QString axis_str = QString::fromStdString(param.Get("axis", "")); + const QString direction_str = QString::fromStdString(param.Get("direction", "")); + + return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str); + } + if (param.Has("button")) { + const QString button_str = QString::number(int(std::log2(param.Get("button", 0)))); + return QObject::tr("GC Button %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + return QObject::tr("[unknown]"); } @@ -117,6 +131,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return {}; } + if (param.Get("engine", "") == "gcpad") { + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left" || dir == "right") { + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + + return QObject::tr("GC Axis %1").arg(axis_x_str); + } + + if (dir == "up" || dir == "down") { + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + + return QObject::tr("GC Axis %1").arg(axis_y_str); + } + + return {}; + } return QObject::tr("[unknown]"); } @@ -418,7 +451,8 @@ void ConfigureInput::UpdateButtonLabels() { analog_map_deadzone_and_modifier_slider_label[analog_id]; if (param.Has("engine")) { - if (param.Get("engine", "") == "sdl") { + const auto engine{param.Get("engine", "")}; + if (engine == "sdl" || engine == "gcpad") { if (!param.Has("deadzone")) { param.Set("deadzone", 0.1f); } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 0a7ad1d2f..9c4a7517f 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,6 +1,10 @@ add_library(input_common STATIC analog_from_button.cpp analog_from_button.h + gcadapter/gc_adapter.cpp + gcadapter/gc_adapter.h + gcadapter/gc_poller.cpp + gcadapter/gc_poller.h keyboard.cpp keyboard.h main.cpp @@ -30,3 +34,4 @@ endif() create_target_directory_groups(input_common) target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) +target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES}) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..74759ea7d --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -0,0 +1,329 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/logging/log.h" +#include "input_common/gcadapter/gc_adapter.h" + +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, +}; + +Adapter::Adapter() { + if (usb_adapter_handle != nullptr) { + return; + } + LOG_INFO(Input, "GC Adapter Initialization started"); + + const int init_res = libusb_init(&libusb_ctx); + if (init_res == LIBUSB_SUCCESS) { + Setup(); + } 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_controllers_status[port] = static_cast(adapter_payload[offset] >> 4); + + 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, + }; + + static constexpr std::array b2_buttons{ + PadButton::PAD_BUTTON_START, + PadButton::PAD_TRIGGER_Z, + PadButton::PAD_TRIGGER_R, + PadButton::PAD_TRIGGER_L, + }; + + 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; + } + + 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(b1_buttons[i]); + } + } + + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + if ((b2 & (1U << j)) != 0) { + pad.button |= static_cast(b2_buttons[j]); + } + } + for (PadAxes axis : axes) { + const std::size_t 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::PadToState(const GCPadStatus& pad, GCState& state) { + for (const auto& button : PadButtonArray) { + const u16 button_value = static_cast(button); + state.buttons.insert_or_assign(button_value, pad.button & button_value); + } + + for (size_t i = 0; i < pad.axis_values.size(); ++i) { + state.axes.insert_or_assign(static_cast(i), pad.axis_values[i]); + } +} + +void Adapter::Read() { + LOG_DEBUG(Input, "GC Adapter Read() thread started"); + + int payload_size; + std::array adapter_payload; + std::array pads; + + while (adapter_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + sizeof(adapter_payload), &payload_size, 16); + + 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); + 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]; + + 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]); + } + std::this_thread::yield(); + } +} + +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); + + // 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); + 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); + } +} + +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; + } + + 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); + 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) { + LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", + kernel_driver_error); + } + } + + if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); + if (interface_claim_error) { + LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + return true; +} + +void 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; + } + + 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; + } else { + output_endpoint = endpoint->bEndpointAddress; + } + } + } + } + // This transfer seems to be responsible for clearing the state of the adapter + // Used to clear the "busy" state of when the device is unexpectedly unplugged + 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); +} + +Adapter::~Adapter() { + Reset(); +} + +void Adapter::Reset() { + if (adapter_thread_running) { + adapter_thread_running = false; + } + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } + + adapter_controllers_status.fill(ControllerTypes::None); + get_origin.fill(true); + + if (usb_adapter_handle) { + libusb_release_interface(usb_adapter_handle, 1); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + } + + if (libusb_ctx) { + libusb_exit(libusb_ctx); + } +} + +bool Adapter::DeviceConnected(std::size_t port) { + return adapter_controllers_status[port] != ControllerTypes::None; +} + +void Adapter::ResetDeviceType(std::size_t port) { + adapter_controllers_status[port] = ControllerTypes::None; +} + +void Adapter::BeginConfiguration() { + get_origin.fill(true); + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Adapter::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +std::array, 4>& Adapter::GetPadQueue() { + return pad_queue; +} + +const std::array, 4>& Adapter::GetPadQueue() const { + return pad_queue; +} + +std::array& Adapter::GetPadState() { + return state; +} + +const std::array& Adapter::GetPadState() const { + return state; +} + +int Adapter::GetOriginValue(int port, int axis) const { + 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 new file mode 100644 index 000000000..dc1e3e398 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h @@ -0,0 +1,132 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/threadsafe_queue.h" + +struct libusb_context; +struct libusb_device; +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, + // Below is for compatibility with "AxisButton" type + PAD_STICK = 0x2000, +}; + +extern const std::array PadButtonArray; + +enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, +}; + +struct GCPadStatus { + u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + + std::array 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}; + u8 axis_value{255}; +}; + +struct GCState { + std::unordered_map buttons; + std::unordered_map axes; +}; + +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(); + /// Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port); + + std::array, 4>& GetPadQueue(); + const std::array, 4>& GetPadQueue() const; + + std::array& GetPadState(); + const std::array& GetPadState() const; + + int GetOriginValue(int port, int axis) const; + +private: + GCPadStatus GetPadStatus(std::size_t port, const std::array& adapter_payload); + + void PadToState(const GCPadStatus& pad, GCState& state); + + void Read(); + + /// Resets status of device connected to port + void ResetDeviceType(std::size_t 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(); + + libusb_device_handle* usb_adapter_handle = nullptr; + + std::thread adapter_input_thread; + bool adapter_thread_running; + + libusb_context* libusb_ctx; + + u8 input_endpoint = 0; + u8 output_endpoint = 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{}; +}; + +} // 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..6fc141eda --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -0,0 +1,272 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/assert.h" +#include "common/threadsafe_queue.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" + +namespace InputCommon { + +class GCButton final : public Input::ButtonDevice { +public: + explicit GCButton(int port_, int 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 false; + } + +private: + const int port; + const int button; + 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) + : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), + gcadapter(adapter), + origin_value(static_cast(adapter->GetOriginValue(port_, axis_))) {} + + 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; + 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 + return axis_value > threshold; + } + return axis_value < -threshold; + } + return false; + } + +private: + const int port; + const int axis; + float threshold; + bool trigger_if_greater; + GCAdapter::Adapter* gcadapter; + const float origin_value; +}; + +GCButtonFactory::GCButtonFactory(std::shared_ptr adapter_) + : adapter(std::move(adapter_)) {} + +GCButton::~GCButton() = default; + +std::unique_ptr GCButtonFactory::Create(const Common::ParamPackage& params) { + const int button_id = params.Get("button", 0); + const int port = params.Get("port", 0); + + constexpr int PAD_STICK_ID = static_cast(GCAdapter::PadButton::PAD_STICK); + + // button is not an axis/stick button + if (button_id != PAD_STICK_ID) { + return std::make_unique(port, button_id, adapter.get()); + } + + // For Axis buttons, used by the binary sticks. + if (button_id == PAD_STICK_ID) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.25f); + 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(port, axis, threshold, trigger_if_greater, + adapter.get()); + } + + UNREACHABLE(); + return nullptr; +} + +Common::ParamPackage GCButtonFactory::GetNextInput() { + 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; + } + } + + // 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; + } + } + } + return params; +} + +void GCButtonFactory::Start() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCButtonFactory::Stop() { + polling = false; + adapter->EndConfiguration(); +} + +class GCAnalog final : public Input::AnalogDevice { +public: + GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, 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(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; + return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); + } + return 0.0f; + } + + std::pair 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 {x, y}; + } + + std::tuple 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 {x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)}; + } + return {0.0f, 0.0f}; + } + +private: + const int port; + const int axis_x; + const int axis_y; + const float deadzone; + GCAdapter::Adapter* gcadapter; + const float origin_value_x; + const float origin_value_y; + const float range; + mutable std::mutex mutex; +}; + +/// An analog device factory that creates analog devices from GC Adapter +GCAnalogFactory::GCAnalogFactory(std::shared_ptr adapter_) + : adapter(std::move(adapter_)) {} + +/** + * 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 GCAnalogFactory::Create(const Common::ParamPackage& params) { + 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 range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + + return std::make_unique(port, axis_x, axis_y, deadzone, adapter.get(), range); +} + +void GCAnalogFactory::Start() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCAnalogFactory::Stop() { + polling = false; + adapter->EndConfiguration(); +} + +Common::ParamPackage GCAnalogFactory::GetNextInput() { + GCAdapter::GCPadStatus pad; + 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((pad.axis_value - 128.0f) / 128.0f) < 0.05) { + 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; + } + } + } + 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..3e0382d30 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h @@ -0,0 +1,67 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/frontend/input.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/main.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, + public Polling::DevicePoller { +public: +public: + explicit GCButtonFactory(std::shared_ptr adapter_); + + std::unique_ptr Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() override; + + /// For device input configuration/polling + void Start() override; + void Stop() override; + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr adapter; + bool polling{false}; +}; + +/// An analog device factory that creates analog devices from GC Adapter +class GCAnalogFactory final : public Input::Factory, + public Polling::DevicePoller { +public: + explicit GCAnalogFactory(std::shared_ptr adapter_); + + std::unique_ptr Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() override; + + /// For device input configuration/polling + void Start() override; + void Stop() override; + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr adapter; + int analog_x_axis{-1}; + int analog_y_axis{-1}; + int controller_number{-1}; + bool polling{false}; +}; + +} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 89e441ddb..4d0671fb6 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -6,6 +6,8 @@ #include #include "common/param_package.h" #include "input_common/analog_from_button.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" @@ -16,12 +18,20 @@ namespace InputCommon { +std::shared_ptr gcbuttons; +std::shared_ptr gcanalog; +std::shared_ptr gcadapter; static std::shared_ptr keyboard; static std::shared_ptr motion_emu; static std::unique_ptr udp; static std::unique_ptr sdl; void Init() { + gcadapter = std::make_shared(); + gcbuttons = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcbuttons); + gcanalog = std::make_shared(gcadapter); + Input::RegisterFactory("gcpad", gcanalog); keyboard = std::make_shared(); Input::RegisterFactory("keyboard", keyboard); Input::RegisterFactory("analog_from_button", @@ -37,6 +47,10 @@ void Init() { } void Shutdown() { + Input::UnregisterFactory("gcpad"); + Input::UnregisterFactory("gcpad"); + gcbuttons.reset(); + gcanalog.reset(); Input::UnregisterFactory("keyboard"); keyboard.reset(); Input::UnregisterFactory("analog_from_button"); @@ -102,6 +116,16 @@ std::vector> GetPollers(DeviceType type) { #ifdef HAVE_SDL2 pollers = sdl->GetPollers(type); #endif + switch (type) { + case DeviceType::Analog: + pollers.push_back(std::make_unique(*gcanalog)); + break; + case DeviceType::Button: + pollers.push_back(std::make_unique(*gcbuttons)); + break; + default: + break; + } return pollers; } From 700fa6b96a72e08dcfd8968a3287a69962be622c Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:50:16 -0500 Subject: [PATCH 3/8] gc_adapter: Port code cleanup and feature updates from yuzu Streamlines the code and introduces fixes for the origin status of the controller along with adapter hotplug support Co-Authored-By: Narr the Reg <5944268+german77@users.noreply.github.com> Co-Authored-By: LC <712067+lioncash@users.noreply.github.com> --- src/input_common/gcadapter/gc_adapter.cpp | 411 ++++++++++++---------- src/input_common/gcadapter/gc_adapter.h | 141 ++++---- src/input_common/gcadapter/gc_poller.cpp | 158 ++++----- 3 files changed, 390 insertions(+), 320 deletions(-) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index 74759ea7d..11a352416 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,201 +4,239 @@ #include #include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include "common/logging/log.h" +#include "common/param_package.h" #include "input_common/gcadapter/gc_adapter.h" 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, -}; - Adapter::Adapter() { if (usb_adapter_handle != nullptr) { return; } - LOG_INFO(Input, "GC Adapter Initialization started"); - 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() { + JoinThreads(); + ClearLibusbHandle(); + ResetDevices(); - adapter_controllers_status[port] = static_cast(adapter_payload[offset] >> 4); + if (libusb_ctx) { + libusb_exit(libusb_ctx); + } +} + +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); + } + 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) { + UpdateSettings(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; - } - - 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(b1_buttons[i]); - } + 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; } - - for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pad.button |= static_cast(b2_buttons[j]); - } - } - for (PadAxes axis : axes) { - const std::size_t 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::PadToState(const GCPadStatus& pad, GCState& state) { - for (const auto& button : PadButtonArray) { - const u16 button_value = static_cast(button); - state.buttons.insert_or_assign(button_value, pad.button & button_value); - } - - for (size_t i = 0; i < pad.axis_values.size(); ++i) { - state.axes.insert_or_assign(static_cast(i), pad.axis_values[i]); + pads[port].axis_values[index] = + static_cast(axis_value - pads[port].axis_origin[index]); } } -void Adapter::Read() { - LOG_DEBUG(Input, "GC Adapter Read() thread started"); +void Adapter::UpdateSettings(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}; - 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]; - - 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]); - } - std::this_thread::yield(); +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; + input_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) { @@ -224,13 +262,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++) { @@ -239,7 +277,7 @@ void Adapter::GetGCEndpoint(libusb_device* device) { 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) { + if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { input_endpoint = endpoint->bEndpointAddress; } else { output_endpoint = endpoint->bEndpointAddress; @@ -252,78 +290,89 @@ 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; } +} - if (libusb_ctx) { - libusb_exit(libusb_ctx); +void Adapter::ResetDevices() { + for (std::size_t i = 0; i < pads.size(); ++i) { + ResetDevice(i); } } -bool Adapter::DeviceConnected(std::size_t port) { - return adapter_controllers_status[port] != ControllerTypes::None; +void Adapter::ResetDevice(std::size_t port) { + pads[port].type = ControllerTypes::None; + pads[port].buttons = 0; + pads[port].last_button = PadButton::Undefined; + pads[port].axis_values.fill(0); + pads[port].axis_origin.fill(255); } -void Adapter::ResetDeviceType(std::size_t port) { - adapter_controllers_status[port] = ControllerTypes::None; +std::vector Adapter::GetInputDevices() const { + std::vector devices; + for (std::size_t port = 0; port < pads.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + std::string name = fmt::format("Gamecube Controller {}", port + 1); + devices.emplace_back(Common::ParamPackage{ + {"class", "gcpad"}, + {"display", std::move(name)}, + {"port", std::to_string(port)}, + }); + } + return devices; +} + +bool Adapter::DeviceConnected(std::size_t port) const { + 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(int port, int 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 dc1e3e398..7b3a6f64b 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -17,27 +17,30 @@ struct libusb_context; struct libusb_device; struct libusb_device_handle; +namespace Common { +class ParamPackage; +} + 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, @@ -48,85 +51,103 @@ 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{}; + 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(); + /// Used for polling void BeginConfiguration(); void EndConfiguration(); + 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); + 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(int port, int axis) const; + std::vector GetInputDevices() const; private: - GCPadStatus GetPadStatus(std::size_t port, const std::array& adapter_payload); + using AdapterPayload = std::array; - void PadToState(const GCPadStatus& pad, GCState& state); + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateSettings(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 Read(); + void AdapterInputThread(); - /// Resets status of device connected to port - void ResetDeviceType(std::size_t port); + void AdapterScanThread(); - /// 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(); + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); /// For use in initialization, querying devices to find the adapter void Setup(); + /// Resets status of all GC controller devices to a disconnected state + void ResetDevices(); + + /// Resets status of device connected to a disconnected 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 endpoint was set correctly + bool GetGCEndpoint(libusb_device* device); + + // 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}; - 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}; }; - } // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 6fc141eda..b0fb2d95a 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -22,7 +22,7 @@ public: bool GetStatus() const override { if (gcadapter->DeviceConnected(port)) { - return gcadapter->GetPadState()[port].buttons.at(button); + return (gcadapter->GetPadState(port).buttons & button) != 0; } return false; } @@ -38,16 +38,13 @@ public: explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, 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 return axis_value > threshold; } return axis_value < -threshold; @@ -56,12 +53,11 @@ public: } private: - const int port; - const int axis; + const u32 port; + const u32 axis; float threshold; bool trigger_if_greater; - GCAdapter::Adapter* gcadapter; - const float origin_value; + const GCAdapter::Adapter* gcadapter; }; GCButtonFactory::GCButtonFactory(std::shared_ptr adapter_) @@ -73,7 +69,7 @@ std::unique_ptr GCButtonFactory::Create(const Common::Param const int button_id = params.Get("button", 0); const int port = 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 +102,25 @@ Common::ParamPackage GCButtonFactory::GetNextInput() { 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; @@ -149,26 +138,23 @@ void GCButtonFactory::Stop() { class GCAnalog final : public Input::AnalogDevice { public: - GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, 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_) {} + explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, float deadzone_, + const GCAdapter::Adapter* adapter) + : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter) {} - float GetAxis(int axis) const { + 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; - return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); + const auto axis_value = + static_cast(gcadapter->GetPadState(port).axis_values.at(axis)); + return (axis_value) / 50.0f; } return 0.0f; } - std::pair GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); - + std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_axis_y); // Make sure the coordinates are in the unit circle, // otherwise normalize it. float r = x * x + y * y; @@ -192,14 +178,11 @@ public: } private: - const int port; - const int axis_x; - const int axis_y; + const u32 port; + const u32 axis_x; + const u32 axis_y; const float deadzone; - GCAdapter::Adapter* gcadapter; - const float origin_value_x; - const float origin_value_y; - const float range; + const GCAdapter::Adapter* gcadapter; mutable std::mutex mutex; }; @@ -215,13 +198,12 @@ GCAnalogFactory::GCAnalogFactory(std::shared_ptr adapter_) * - "axis_y": the index of the axis to be bind as y-axis */ std::unique_ptr GCAnalogFactory::Create(const Common::ParamPackage& params) { - 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 range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + const auto port = static_cast(params.Get("port", 0)); + const auto axis_x = static_cast(params.Get("axis_x", 0)); + const auto axis_y = static_cast(params.Get("axis_y", 1)); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - return std::make_unique(port, axis_x, axis_y, deadzone, adapter.get(), range); + return std::make_unique(port, axis_x, axis_y, deadzone, adapter.get()); } void GCAnalogFactory::Start() { @@ -236,26 +218,44 @@ void GCAnalogFactory::Stop() { 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((pad.axis_value - 128.0f) / 128.0f) < 0.05) { - 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); From 32f8a565f3747a4049d0119dcfb69a2402cacefc Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:00:18 -0500 Subject: [PATCH 4/8] configure_input: Add frontend text for GC adapter Also streamlines some of the frontend text logic. --- .../configuration/configure_input.cpp | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 6eb960c26..60d30c599 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -56,12 +56,12 @@ static QString ButtonToText(const Common::ParamPackage& param) { if (!param.Has("engine")) { return QObject::tr("[not set]"); } - - if (param.Get("engine", "") == "keyboard") { + const auto engine_str = param.Get("engine", ""); + if (engine_str == "keyboard") { return GetKeyName(param.Get("code", 0)); } - if (param.Get("engine", "") == "sdl") { + if (engine_str == "sdl") { if (param.Has("hat")) { const QString hat_str = QString::fromStdString(param.Get("hat", "")); const QString direction_str = QString::fromStdString(param.Get("direction", "")); @@ -85,7 +85,7 @@ static QString ButtonToText(const Common::ParamPackage& param) { return {}; } - if (param.Get("engine", "") == "gcpad") { + if (engine_str == "gcpad") { if (param.Has("axis")) { const QString axis_str = QString::fromStdString(param.Get("axis", "")); const QString direction_str = QString::fromStdString(param.Get("direction", "")); @@ -107,47 +107,31 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return QObject::tr("[not set]"); } - if (param.Get("engine", "") == "analog_from_button") { + const auto engine_str = param.Get("engine", ""); + if (engine_str == "analog_from_button") { return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - if (param.Get("engine", "") == "sdl") { + const QString axis_x_str{QString::fromStdString(param.Get("axis_x", ""))}; + const QString axis_y_str{QString::fromStdString(param.Get("axis_y", ""))}; + static const QString plus_str{QString::fromStdString("+")}; + static const QString minus_str{QString::fromStdString("-")}; + if (engine_str == "sdl" || engine_str == "gcpad") { if (dir == "modifier") { return QObject::tr("[unused]"); } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Axis %1").arg(axis_x_str); + if (dir == "left") { + return QObject::tr("Axis %1%2").arg(axis_x_str, minus_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Axis %1").arg(axis_y_str); + if (dir == "right") { + return QObject::tr("Axis %1%2").arg(axis_x_str, plus_str); } - - return {}; - } - - if (param.Get("engine", "") == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); + if (dir == "up") { + return QObject::tr("Axis %1%2").arg(axis_y_str, plus_str); } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("GC Axis %1").arg(axis_x_str); + if (dir == "down") { + return QObject::tr("Axis %1%2").arg(axis_y_str, minus_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("GC Axis %1").arg(axis_y_str); - } - return {}; } return QObject::tr("[unknown]"); @@ -456,21 +440,16 @@ void ConfigureInput::UpdateButtonLabels() { if (!param.Has("deadzone")) { param.Set("deadzone", 0.1f); } - - analog_stick_slider->setValue(static_cast(param.Get("deadzone", 0.1f) * 100)); - if (analog_stick_slider->value() == 0) { - analog_stick_slider_label->setText(tr("Deadzone: 0%")); - } + const auto slider_value = static_cast(param.Get("deadzone", 0.1f) * 100); + analog_stick_slider_label->setText(tr("Deadzone: %1%").arg(slider_value)); + analog_stick_slider->setValue(slider_value); } else { if (!param.Has("modifier_scale")) { param.Set("modifier_scale", 0.5f); } - - analog_stick_slider->setValue( - static_cast(param.Get("modifier_scale", 0.5f) * 100)); - if (analog_stick_slider->value() == 0) { - analog_stick_slider_label->setText(tr("Modifier Scale: 0%")); - } + const auto slider_value = static_cast(param.Get("modifier_scale", 0.5f) * 100); + analog_stick_slider_label->setText(tr("Modifier Scale: %1%").arg(slider_value)); + analog_stick_slider->setValue(slider_value); } } } From 161e6a541bd2b35a5f03ae7fd46a9e714a774ea6 Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Tue, 7 Jul 2020 10:13:15 +0000 Subject: [PATCH 5/8] cmake: unbreak system libusb support Reference libusb doesn't support DragonFly and FreeBSD because those ship a different libusb implementation (supports 0.1, 1.0, 2.0 API). --- externals/cmake-modules/FindLibUSB.cmake | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 externals/cmake-modules/FindLibUSB.cmake diff --git a/externals/cmake-modules/FindLibUSB.cmake b/externals/cmake-modules/FindLibUSB.cmake new file mode 100644 index 000000000..dec0b98b0 --- /dev/null +++ b/externals/cmake-modules/FindLibUSB.cmake @@ -0,0 +1,43 @@ +# - Find libusb-1.0 library +# This module defines +# LIBUSB_INCLUDE_DIR, where to find bluetooth.h +# LIBUSB_LIBRARIES, the libraries needed to use libusb-1.0. +# LIBUSB_FOUND, If false, do not try to use libusb-1.0. +# +# Copyright (c) 2009, Michal Cihar, +# +# vim: expandtab sw=4 ts=4 sts=4: + +if(ANDROID) + set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found") + message(STATUS "libusb-1.0 not found.") +elseif (NOT LIBUSB_FOUND) + pkg_check_modules (LIBUSB_PKG libusb-1.0) + + find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h + PATHS + ${LIBUSB_PKG_INCLUDE_DIRS} + /usr/include/libusb-1.0 + /usr/include + /usr/local/include/libusb-1.0 + /usr/local/include + ) + + find_library(LIBUSB_LIBRARIES NAMES usb-1.0 usb + PATHS + ${LIBUSB_PKG_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + ) + + if(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) + set(LIBUSB_FOUND TRUE CACHE INTERNAL "libusb-1.0 found") + message(STATUS "Found libusb-1.0: ${LIBUSB_INCLUDE_DIR}, ${LIBUSB_LIBRARIES}") + else(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) + set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found") + message(STATUS "libusb-1.0 not found.") + endif(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES) + + mark_as_advanced(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES) +endif () + From a80e5664645a3dbb8bbf9dfacb54a056a92dcfb6 Mon Sep 17 00:00:00 2001 From: ameerj <52414509+ameerj@users.noreply.github.com> Date: Fri, 12 Mar 2021 14:32:30 -0500 Subject: [PATCH 6/8] gcadapter: Implement auto map feature Implements the auto map functionality for the GC adapter. The controls map nicely to the original 3ds controls, with the select button being mapped to the Z button on GC. The ZL/ZR buttons are not mapped by this feature. --- externals/catch | 2 +- externals/dynarmic | 2 +- .../configuration/configure_input.cpp | 9 ++-- src/input_common/gcadapter/gc_poller.cpp | 53 ++++++++++++++++++- src/input_common/gcadapter/gc_poller.h | 3 ++ src/input_common/main.cpp | 30 ++++++++--- src/input_common/main.h | 6 +-- 7 files changed, 85 insertions(+), 20 deletions(-) diff --git a/externals/catch b/externals/catch index de6fe184a..15cf3caac 160000 --- a/externals/catch +++ b/externals/catch @@ -1 +1 @@ -Subproject commit de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad +Subproject commit 15cf3caaceb21172ea42a24e595a2eb58c3ec960 diff --git a/externals/dynarmic b/externals/dynarmic index 358cf6f03..f9d84871f 160000 --- a/externals/dynarmic +++ b/externals/dynarmic @@ -1 +1 @@ -Subproject commit 358cf6f0357baae3e3bb5788431acf1068f897b5 +Subproject commit f9d84871fb6dd41c47945d649dc9017aa3762125 diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 60d30c599..388d700f7 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -274,7 +274,8 @@ ConfigureInput::ConfigureInput(QWidget* parent) }); connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); - if (analogs_param[analog_id].Get("engine", "") == "sdl") { + const auto engine = analogs_param[analog_id].Get("engine", ""); + if (engine == "sdl" || engine == "gcpad") { analog_map_deadzone_and_modifier_slider_label[analog_id]->setText( tr("Deadzone: %1%").arg(slider_value)); analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); @@ -461,16 +462,14 @@ void ConfigureInput::MapFromButton(const Common::ParamPackage& params) { Common::ParamPackage aux_param; bool mapped = false; for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - aux_param = InputCommon::GetSDLControllerButtonBindByGUID(params.Get("guid", "0"), - params.Get("port", 0), button_id); + aux_param = InputCommon::GetControllerButtonBinds(params, button_id); if (aux_param.Has("engine")) { buttons_param[button_id] = aux_param; mapped = true; } } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - aux_param = InputCommon::GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"), - params.Get("port", 0), analog_id); + aux_param = InputCommon::GetControllerAnalogBinds(params, analog_id); if (aux_param.Has("engine")) { analogs_param[analog_id] = aux_param; mapped = true; diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index b0fb2d95a..e0345cb55 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -12,7 +12,27 @@ #include "input_common/gcadapter/gc_poller.h" namespace InputCommon { - +namespace { +constexpr std::array gc_to_3ds_mapping{{ + GCAdapter::PadButton::ButtonA, + GCAdapter::PadButton::ButtonB, + GCAdapter::PadButton::ButtonX, + GCAdapter::PadButton::ButtonY, + GCAdapter::PadButton::ButtonUp, + GCAdapter::PadButton::ButtonDown, + GCAdapter::PadButton::ButtonLeft, + GCAdapter::PadButton::ButtonRight, + GCAdapter::PadButton::TriggerL, + GCAdapter::PadButton::TriggerR, + GCAdapter::PadButton::ButtonStart, + GCAdapter::PadButton::TriggerZ, + GCAdapter::PadButton::Undefined, + GCAdapter::PadButton::Undefined, + GCAdapter::PadButton::Undefined, + GCAdapter::PadButton::Undefined, + GCAdapter::PadButton::Undefined, +}}; +} class GCButton final : public Input::ButtonDevice { public: explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) @@ -126,6 +146,17 @@ Common::ParamPackage GCButtonFactory::GetNextInput() { return params; } +Common::ParamPackage GCButtonFactory::GetGcTo3DSMappedButton( + int port, Settings::NativeButton::Values button) { + Common::ParamPackage params({{"engine", "gcpad"}}); + params.Set("port", port); + auto mapped_button = gc_to_3ds_mapping[static_cast(button)]; + if (mapped_button != GCAdapter::PadButton::Undefined) { + params.Set("button", static_cast(mapped_button)); + } + return params; +} + void GCButtonFactory::Start() { polling = true; adapter->BeginConfiguration(); @@ -269,4 +300,24 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() { return params; } +Common::ParamPackage GCAnalogFactory::GetGcTo3DSMappedAnalog( + int port, Settings::NativeAnalog::Values analog) { + int x_axis, y_axis; + Common::ParamPackage params({{"engine", "gcpad"}}); + params.Set("port", port); + if (analog == Settings::NativeAnalog::Values::CirclePad) { + x_axis = static_cast(GCAdapter::PadAxes::StickX); + y_axis = static_cast(GCAdapter::PadAxes::StickY); + } else if (analog == Settings::NativeAnalog::Values::CStick) { + x_axis = static_cast(GCAdapter::PadAxes::SubstickX); + y_axis = static_cast(GCAdapter::PadAxes::SubstickY); + } else { + LOG_WARNING(Input, "analog value out of range {}", analog); + return {{}}; + } + params.Set("axis_x", x_axis); + params.Set("axis_y", y_axis); + return params; +} + } // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 3e0382d30..113e200f9 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/src/input_common/gcadapter/gc_poller.h @@ -6,6 +6,7 @@ #include #include "core/frontend/input.h" +#include "core/settings.h" #include "input_common/gcadapter/gc_adapter.h" #include "input_common/main.h" @@ -24,6 +25,7 @@ public: std::unique_ptr Create(const Common::ParamPackage& params) override; Common::ParamPackage GetNextInput() override; + Common::ParamPackage GetGcTo3DSMappedButton(int port, Settings::NativeButton::Values button); /// For device input configuration/polling void Start() override; @@ -47,6 +49,7 @@ public: std::unique_ptr Create(const Common::ParamPackage& params) override; Common::ParamPackage GetNextInput() override; + Common::ParamPackage GetGcTo3DSMappedAnalog(int port, Settings::NativeAnalog::Values analog); /// For device input configuration/polling void Start() override; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 4d0671fb6..68a430f43 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -91,16 +91,30 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, return circle_pad_param.Serialize(); } -Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, - int button) { - return dynamic_cast(sdl.get())->GetSDLControllerButtonBindByGUID( - guid, port, static_cast(button)); +Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button) { + const auto native_button{static_cast(button)}; + const auto engine{params.Get("engine", "")}; + if (engine == "sdl") { + return dynamic_cast(sdl.get())->GetSDLControllerButtonBindByGUID( + params.Get("guid", "0"), params.Get("port", 0), native_button); + } + if (engine == "gcpad") { + return gcbuttons->GetGcTo3DSMappedButton(params.Get("port", 0), native_button); + } + return {}; } -Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, - int analog) { - return dynamic_cast(sdl.get())->GetSDLControllerAnalogBindByGUID( - guid, port, static_cast(analog)); +Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog) { + const auto native_analog{static_cast(analog)}; + const auto engine{params.Get("engine", "")}; + if (engine == "sdl") { + return dynamic_cast(sdl.get())->GetSDLControllerAnalogBindByGUID( + params.Get("guid", "0"), params.Get("port", 0), native_analog); + } + if (engine == "gcpad") { + return gcanalog->GetGcTo3DSMappedAnalog(params.Get("port", 0), native_analog); + } + return {}; } void ReloadInputDevices() { diff --git a/src/input_common/main.h b/src/input_common/main.h index 606b198a8..48ecd26ba 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -37,10 +37,8 @@ std::string GenerateKeyboardParam(int key_code); std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, int key_modifier, float modifier_scale); -Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, - int button); -Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, - int analog); +Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button); +Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog); /// Reloads the input devices void ReloadInputDevices(); From 48fad3a903b40fd350d377d695b4ad04d8286d8f Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Mon, 15 Mar 2021 10:51:46 -0400 Subject: [PATCH 7/8] libusb: Add /utf-8 compile option for MSVC. --- externals/libusb/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 074ce01d3..0d8323744 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -1,3 +1,8 @@ +# Ensure libusb compiles with UTF-8 encoding on MSVC +if(MSVC) + add_compile_options(/utf-8) +endif() + add_library(usb STATIC EXCLUDE_FROM_ALL libusb/libusb/core.c libusb/libusb/core.c From 6a124bc317b02713b7d036d1756195fd5441f2bc Mon Sep 17 00:00:00 2001 From: lat9nq Date: Sat, 24 Apr 2021 17:23:45 -0400 Subject: [PATCH 8/8] input_common: Fix libusb include on Arch --- src/input_common/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 9c4a7517f..ac14dd89e 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -34,4 +34,5 @@ endif() create_target_directory_groups(input_common) target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) +target_include_directories(input_common PRIVATE ${LIBUSB_INCLUDE_DIR}) target_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES})