Merge pull request #5735 from ameerj/gc-input

input_common: Implement official GameCube adapter support
This commit is contained in:
bunnei 2021-04-28 21:15:55 -07:00 committed by GitHub
commit 86b775bd8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1326 additions and 48 deletions

3
.gitmodules vendored
View File

@ -31,6 +31,9 @@
[submodule "libressl"] [submodule "libressl"]
path = externals/libressl path = externals/libressl
url = https://github.com/citra-emu/ext-libressl-portable.git 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"] [submodule "cubeb"]
path = externals/cubeb path = externals/cubeb
url = https://github.com/kinetiknz/cubeb.git url = https://github.com/kinetiknz/cubeb.git

View File

@ -193,6 +193,17 @@ if (ENABLE_QT)
endif() endif()
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 (ENABLE_FFMPEG)
if (CITRA_USE_BUNDLED_FFMPEG) if (CITRA_USE_BUNDLED_FFMPEG)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64) if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)

2
externals/catch vendored

@ -1 +1 @@
Subproject commit de6fe184a9ac1a06895cdd1c9b437f0a0bdf14ad Subproject commit 15cf3caaceb21172ea42a24e595a2eb58c3ec960

View File

@ -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, <michal@cihar.com>
#
# 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 ()

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 358cf6f0357baae3e3bb5788431acf1068f897b5 Subproject commit f9d84871fb6dd41c47945d649dc9017aa3762125

152
externals/libusb/CMakeLists.txt vendored Normal file
View File

@ -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)

90
externals/libusb/config.h.in vendored Normal file
View File

@ -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 <asm/types.h> 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 <linux/filter.h> header file. */
#cmakedefine HAVE_LINUX_FILTER_H 1
/* Define to 1 if you have the <linux/netlink.h> header file. */
#cmakedefine HAVE_LINUX_NETLINK_H 1
/* Define to 1 if you have the <poll.h> header file. */
#cmakedefine HAVE_POLL_H 1
/* Define to 1 if you have the <signal.h> header file. */
#cmakedefine HAVE_SIGNAL_H 1
/* Define to 1 if you have the <strings.h> 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 <syslog.h> header file. */
#cmakedefine HAVE_SYSLOG_H 1
/* Define to 1 if you have the <sys/socket.h> header file. */
#cmakedefine HAVE_SYS_SOCKET_H 1
/* Define to 1 if you have the <sys/time.h> header file. */
#cmakedefine HAVE_SYS_TIME_H 1
/* Define to 1 if you have the <sys/types.h> 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

1
externals/libusb/libusb vendored Submodule

@ -0,0 +1 @@
Subproject commit e782eeb2514266f6738e242cdcb18e3ae1ed06fa

View File

@ -56,12 +56,12 @@ static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) { if (!param.Has("engine")) {
return QObject::tr("[not set]"); return QObject::tr("[not set]");
} }
const auto engine_str = param.Get("engine", "");
if (param.Get("engine", "") == "keyboard") { if (engine_str == "keyboard") {
return GetKeyName(param.Get("code", 0)); return GetKeyName(param.Get("code", 0));
} }
if (param.Get("engine", "") == "sdl") { if (engine_str == "sdl") {
if (param.Has("hat")) { if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", "")); const QString hat_str = QString::fromStdString(param.Get("hat", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", "")); const QString direction_str = QString::fromStdString(param.Get("direction", ""));
@ -85,6 +85,20 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return {}; 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]"); return QObject::tr("[unknown]");
} }
@ -93,30 +107,33 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return QObject::tr("[not set]"); 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, "")}); 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") { if (dir == "modifier") {
return QObject::tr("[unused]"); return QObject::tr("[unused]");
} }
if (dir == "left") {
if (dir == "left" || dir == "right") { return QObject::tr("Axis %1%2").arg(axis_x_str, minus_str);
const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
return QObject::tr("Axis %1").arg(axis_x_str);
} }
if (dir == "right") {
if (dir == "up" || dir == "down") { return QObject::tr("Axis %1%2").arg(axis_x_str, plus_str);
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); }
if (dir == "up") {
return QObject::tr("Axis %1").arg(axis_y_str); 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 {};
} }
return QObject::tr("[unknown]"); return QObject::tr("[unknown]");
} }
@ -257,7 +274,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
}); });
connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] { connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value(); 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( analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
tr("Deadzone: %1%").arg(slider_value)); tr("Deadzone: %1%").arg(slider_value));
analogs_param[analog_id].Set("deadzone", slider_value / 100.0f); 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]; analog_map_deadzone_and_modifier_slider_label[analog_id];
if (param.Has("engine")) { if (param.Has("engine")) {
if (param.Get("engine", "") == "sdl") { const auto engine{param.Get("engine", "")};
if (engine == "sdl" || engine == "gcpad") {
if (!param.Has("deadzone")) { if (!param.Has("deadzone")) {
param.Set("deadzone", 0.1f); param.Set("deadzone", 0.1f);
} }
const auto slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
analog_stick_slider->setValue(static_cast<int>(param.Get("deadzone", 0.1f) * 100)); analog_stick_slider_label->setText(tr("Deadzone: %1%").arg(slider_value));
if (analog_stick_slider->value() == 0) { analog_stick_slider->setValue(slider_value);
analog_stick_slider_label->setText(tr("Deadzone: 0%"));
}
} else { } else {
if (!param.Has("modifier_scale")) { if (!param.Has("modifier_scale")) {
param.Set("modifier_scale", 0.5f); param.Set("modifier_scale", 0.5f);
} }
const auto slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
analog_stick_slider->setValue( analog_stick_slider_label->setText(tr("Modifier Scale: %1%").arg(slider_value));
static_cast<int>(param.Get("modifier_scale", 0.5f) * 100)); analog_stick_slider->setValue(slider_value);
if (analog_stick_slider->value() == 0) {
analog_stick_slider_label->setText(tr("Modifier Scale: 0%"));
}
} }
} }
} }
@ -448,16 +462,14 @@ void ConfigureInput::MapFromButton(const Common::ParamPackage& params) {
Common::ParamPackage aux_param; Common::ParamPackage aux_param;
bool mapped = false; bool mapped = false;
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
aux_param = InputCommon::GetSDLControllerButtonBindByGUID(params.Get("guid", "0"), aux_param = InputCommon::GetControllerButtonBinds(params, button_id);
params.Get("port", 0), button_id);
if (aux_param.Has("engine")) { if (aux_param.Has("engine")) {
buttons_param[button_id] = aux_param; buttons_param[button_id] = aux_param;
mapped = true; mapped = true;
} }
} }
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
aux_param = InputCommon::GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"), aux_param = InputCommon::GetControllerAnalogBinds(params, analog_id);
params.Get("port", 0), analog_id);
if (aux_param.Has("engine")) { if (aux_param.Has("engine")) {
analogs_param[analog_id] = aux_param; analogs_param[analog_id] = aux_param;
mapped = true; mapped = true;

View File

@ -1,6 +1,10 @@
add_library(input_common STATIC add_library(input_common STATIC
analog_from_button.cpp analog_from_button.cpp
analog_from_button.h analog_from_button.h
gcadapter/gc_adapter.cpp
gcadapter/gc_adapter.h
gcadapter/gc_poller.cpp
gcadapter/gc_poller.h
keyboard.cpp keyboard.cpp
keyboard.h keyboard.h
main.cpp main.cpp
@ -30,3 +34,5 @@ endif()
create_target_directory_groups(input_common) create_target_directory_groups(input_common)
target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) 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})

View File

@ -0,0 +1,378 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <chrono>
#include <thread>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
#endif
#include <libusb.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include "common/logging/log.h"
#include "common/param_package.h"
#include "input_common/gcadapter/gc_adapter.h"
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<s32>(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<s32>(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<ControllerTypes>(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<PadButton, 8> b1_buttons{
PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
};
static constexpr std::array<PadButton, 4> 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<u16>(pads[port].buttons | static_cast<u16>(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<u16>(pads[port].buttons | static_cast<u16>(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<PadAxes, 6> axes{
PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
};
for (const PadAxes axis : axes) {
const auto index = static_cast<std::size_t>(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<s16>(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<PadAxes>(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<Common::ParamPackage> Adapter::GetInputDevices() const {
std::vector<Common::ParamPackage> 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<GCPadStatus>& Adapter::GetPadQueue() {
return pad_queue;
}
const Common::SPSCQueue<GCPadStatus>& 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

View File

@ -0,0 +1,153 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <array>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_map>
#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<s16, 6> axis_values{};
std::array<u8, 6> axis_origin{};
};
class Adapter {
public:
Adapter();
~Adapter();
/// Used for polling
void BeginConfiguration();
void EndConfiguration();
Common::SPSCQueue<GCPadStatus>& GetPadQueue();
const Common::SPSCQueue<GCPadStatus>& 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<Common::ParamPackage> GetInputDevices() const;
private:
using AdapterPayload = std::array<u8, 37>;
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<GCController, 4> pads;
Common::SPSCQueue<GCPadStatus> 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

View File

@ -0,0 +1,323 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <list>
#include <mutex>
#include <utility>
#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<GCAdapter::PadButton, Settings::NativeButton::NumButtons> 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<GCAdapter::Adapter> adapter_)
: adapter(std::move(adapter_)) {}
GCButton::~GCButton() = default;
std::unique_ptr<Input::ButtonDevice> 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<s32>(GCAdapter::PadButton::Stick);
// button is not an axis/stick button
if (button_id != PAD_STICK_ID) {
return std::make_unique<GCButton>(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<GCAxisButton>(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<s32>(pad.port));
if (pad.button != GCAdapter::PadButton::Undefined) {
params.Set("button", static_cast<u16>(pad.button));
}
// For Axis button implementation
if (pad.axis != GCAdapter::PadAxes::Undefined) {
params.Set("axis", static_cast<u8>(pad.axis));
params.Set("button", static_cast<u16>(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<int>(button)];
if (mapped_button != GCAdapter::PadButton::Undefined) {
params.Set("button", static_cast<u16>(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<float>(gcadapter->GetPadState(port).axis_values.at(axis));
return (axis_value) / 50.0f;
}
return 0.0f;
}
std::pair<float, float> 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<float, float> GetStatus() const override {
const auto [x, y] = GetAnalog(axis_x, axis_y);
const float r = std::sqrt((x * x) + (y * y));
if (r > deadzone) {
return {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<GCAdapter::Adapter> 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<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
const auto port = static_cast<u32>(params.Get("port", 0));
const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
return std::make_unique<GCAnalog>(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<s32>(pad.port));
params.Set("button", static_cast<u16>(pad.button));
return params;
}
if (pad.axis == GCAdapter::PadAxes::Undefined ||
std::abs(static_cast<float>(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<u8>(pad.axis);
if (axis == 0 || axis == 1) {
analog_x_axis = 0;
analog_y_axis = 1;
controller_number = static_cast<s32>(pad.port);
break;
}
if (axis == 2 || axis == 3) {
analog_x_axis = 2;
analog_y_axis = 3;
controller_number = static_cast<s32>(pad.port);
break;
}
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<s32>(pad.port);
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
controller_number == static_cast<s32>(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<s32>(GCAdapter::PadAxes::StickX);
y_axis = static_cast<s32>(GCAdapter::PadAxes::StickY);
} else if (analog == Settings::NativeAnalog::Values::CStick) {
x_axis = static_cast<s32>(GCAdapter::PadAxes::SubstickX);
y_axis = static_cast<s32>(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

View File

@ -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 <memory>
#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<Input::ButtonDevice>,
public Polling::DevicePoller {
public:
public:
explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
std::unique_ptr<Input::ButtonDevice> 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<GCAdapter::Adapter> adapter;
bool polling{false};
};
/// An analog device factory that creates analog devices from GC Adapter
class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice>,
public Polling::DevicePoller {
public:
explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
std::unique_ptr<Input::AnalogDevice> 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<GCAdapter::Adapter> adapter;
int analog_x_axis{-1};
int analog_y_axis{-1};
int controller_number{-1};
bool polling{false};
};
} // namespace InputCommon

View File

@ -6,6 +6,8 @@
#include <thread> #include <thread>
#include "common/param_package.h" #include "common/param_package.h"
#include "input_common/analog_from_button.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/keyboard.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "input_common/motion_emu.h" #include "input_common/motion_emu.h"
@ -16,12 +18,20 @@
namespace InputCommon { namespace InputCommon {
std::shared_ptr<GCButtonFactory> gcbuttons;
std::shared_ptr<GCAnalogFactory> gcanalog;
std::shared_ptr<GCAdapter::Adapter> gcadapter;
static std::shared_ptr<Keyboard> keyboard; static std::shared_ptr<Keyboard> keyboard;
static std::shared_ptr<MotionEmu> motion_emu; static std::shared_ptr<MotionEmu> motion_emu;
static std::unique_ptr<CemuhookUDP::State> udp; static std::unique_ptr<CemuhookUDP::State> udp;
static std::unique_ptr<SDL::State> sdl; static std::unique_ptr<SDL::State> sdl;
void Init() { void Init() {
gcadapter = std::make_shared<GCAdapter::Adapter>();
gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
keyboard = std::make_shared<Keyboard>(); keyboard = std::make_shared<Keyboard>();
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
@ -37,6 +47,10 @@ void Init() {
} }
void Shutdown() { void Shutdown() {
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
gcbuttons.reset();
gcanalog.reset();
Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
keyboard.reset(); keyboard.reset();
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
@ -77,16 +91,30 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
return circle_pad_param.Serialize(); return circle_pad_param.Serialize();
} }
Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button) {
int button) { const auto native_button{static_cast<Settings::NativeButton::Values>(button)};
const auto engine{params.Get("engine", "")};
if (engine == "sdl") {
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID( return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID(
guid, port, static_cast<Settings::NativeButton::Values>(button)); 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, Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog) {
int analog) { const auto native_analog{static_cast<Settings::NativeAnalog::Values>(analog)};
const auto engine{params.Get("engine", "")};
if (engine == "sdl") {
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID( return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID(
guid, port, static_cast<Settings::NativeAnalog::Values>(analog)); 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() { void ReloadInputDevices() {
@ -102,6 +130,16 @@ std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
pollers = sdl->GetPollers(type); pollers = sdl->GetPollers(type);
#endif #endif
switch (type) {
case DeviceType::Analog:
pollers.push_back(std::make_unique<GCAnalogFactory>(*gcanalog));
break;
case DeviceType::Button:
pollers.push_back(std::make_unique<GCButtonFactory>(*gcbuttons));
break;
default:
break;
}
return pollers; return pollers;
} }

View File

@ -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, std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
int key_modifier, float modifier_scale); int key_modifier, float modifier_scale);
Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button);
int button); Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog);
Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port,
int analog);
/// Reloads the input devices /// Reloads the input devices
void ReloadInputDevices(); void ReloadInputDevices();