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/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/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/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 () + 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/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt new file mode 100644 index 000000000..0d8323744 --- /dev/null +++ b/externals/libusb/CMakeLists.txt @@ -0,0 +1,152 @@ +# 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 + 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 diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 64fb7c840..388d700f7 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,6 +85,20 @@ static QString ButtonToText(const Common::ParamPackage& param) { return {}; } + 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", "")); + + 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]"); } @@ -93,30 +107,33 @@ 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); + } + if (dir == "up") { + return QObject::tr("Axis %1%2").arg(axis_y_str, plus_str); + } + if (dir == "down") { + return QObject::tr("Axis %1%2").arg(axis_y_str, minus_str); } - return {}; } - return QObject::tr("[unknown]"); } @@ -257,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); @@ -418,25 +436,21 @@ 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); } - - 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); } } } @@ -448,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/CMakeLists.txt b/src/input_common/CMakeLists.txt index 0a7ad1d2f..ac14dd89e 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,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}) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..11a352416 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -0,0 +1,378 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#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 { + +Adapter::Adapter() { + if (usb_adapter_handle != nullptr) { + return; + } + const int init_res = libusb_init(&libusb_ctx); + if (init_res == LIBUSB_SUCCESS) { + adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + } else { + LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); + } +} + +Adapter::~Adapter() { + JoinThreads(); + ClearLibusbHandle(); + ResetDevices(); + + 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::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, + PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, + }; + + static constexpr std::array b2_buttons{ + 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, + }; + + 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]); + } +} + +void Adapter::UpdateSettings(std::size_t port) { + if (port >= pads.size()) { + return; + } + + constexpr u8 axis_threshold = 50; + GCPadStatus pad_status = {port}; + + if (pads[port].buttons != 0) { + pad_status.button = pads[port].last_button; + pad_queue.Push(pad_status); + } + + // 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); + } + } +} + +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() { + usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); + + if (usb_adapter_handle == NULL) { + return; + } + if (!CheckDeviceAccess()) { + ClearLibusbHandle(); + return; + } + + 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() { + // 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); + } + + 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) { + 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; +} + +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 false; + } + + 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) != 0) { + 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); + return true; +} + +void Adapter::JoinThreads() { + restart_scan_thread = false; + adapter_input_thread_running = false; + adapter_scan_thread_running = false; + + if (adapter_scan_thread.joinable()) { + adapter_scan_thread.join(); + } + + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } +} + +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].buttons = 0; + pads[port].last_button = PadButton::Undefined; + pads[port].axis_values.fill(0); + pads[port].axis_origin.fill(255); +} + +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() { + pad_queue.Clear(); + configuring = true; +} + +void Adapter::EndConfiguration() { + pad_queue.Clear(); + configuring = false; +} + +Common::SPSCQueue& Adapter::GetPadQueue() { + return pad_queue; +} + +const Common::SPSCQueue& Adapter::GetPadQueue() const { + return pad_queue; +} + +GCController& Adapter::GetPadState(std::size_t port) { + return pads.at(port); +} + +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 new file mode 100644 index 000000000..7b3a6f64b --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h @@ -0,0 +1,153 @@ +// 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 Common { +class ParamPackage; +} + +namespace GCAdapter { + +enum class PadButton { + 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 + Stick = 0x2000, +}; + +enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, +}; + +enum class ControllerTypes { + None, + Wired, + Wireless, +}; + +struct GCPadStatus { + std::size_t port{}; + + PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + + PadAxes axis{PadAxes::Undefined}; + s16 axis_value{}; + u8 axis_threshold{50}; +}; + +struct GCController { + ControllerTypes type{}; + u16 buttons{}; + PadButton last_button{}; + std::array axis_values{}; + std::array axis_origin{}; +}; + +class Adapter { +public: + 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) const; + + std::vector GetInputDevices() const; + +private: + using AdapterPayload = std::array; + + 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 AdapterInputThread(); + + void AdapterScanThread(); + + 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; + 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_error_counter{0}; + + bool configuring{false}; +}; +} // 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..e0345cb55 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -0,0 +1,323 @@ +// 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 { +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) + : port(port_), button(button_), gcadapter(adapter) {} + + ~GCButton() override; + + bool GetStatus() const override { + if (gcadapter->DeviceConnected(port)) { + return (gcadapter->GetPadState(port).buttons & button) != 0; + } + 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) {} + + bool GetStatus() const override { + if (gcadapter->DeviceConnected(port)) { + 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) { + return axis_value > threshold; + } + return axis_value < -threshold; + } + return false; + } + +private: + const u32 port; + const u32 axis; + float threshold; + bool trigger_if_greater; + const GCAdapter::Adapter* gcadapter; +}; + +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 s32 PAD_STICK_ID = static_cast(GCAdapter::PadButton::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(); + 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::Stick)); + params.Set("threshold", "0.25"); + if (pad.axis_value > 0) { + params.Set("direction", "+"); + } else { + params.Set("direction", "-"); + } + break; + } + } + 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(); +} + +void GCButtonFactory::Stop() { + polling = false; + adapter->EndConfiguration(); +} + +class GCAnalog final : public Input::AnalogDevice { +public: + 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(u32 axis) const { + if (gcadapter->DeviceConnected(port)) { + std::lock_guard lock{mutex}; + const auto axis_value = + static_cast(gcadapter->GetPadState(port).axis_values.at(axis)); + return (axis_value) / 50.0f; + } + return 0.0f; + } + + 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; + 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 u32 port; + const u32 axis_x; + const u32 axis_y; + const float deadzone; + const GCAdapter::Adapter* gcadapter; + 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 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()); +} + +void GCAnalogFactory::Start() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCAnalogFactory::Stop() { + polling = false; + adapter->EndConfiguration(); +} + +Common::ParamPackage GCAnalogFactory::GetNextInput() { + GCAdapter::GCPadStatus pad; + Common::ParamPackage params; + auto& queue = adapter->GetPadQueue(); + 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; + } + } + 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; +} + +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 new file mode 100644 index 000000000..113e200f9 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h @@ -0,0 +1,70 @@ +// 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 "core/settings.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; + Common::ParamPackage GetGcTo3DSMappedButton(int port, Settings::NativeButton::Values button); + + /// 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; + Common::ParamPackage GetGcTo3DSMappedAnalog(int port, Settings::NativeAnalog::Values analog); + + /// 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..68a430f43 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"); @@ -77,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() { @@ -102,6 +130,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; } 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();