input_common: Add support for GameCube Adapter

This is a port of the initial GameCube adapter input support i added into yuzu emulator.
It requires the same setup as when it was first introduced in yuzu, requiring the Zadig driver be installed for the adapter to allow it to interface with libusb.
This commit is contained in:
ameerj 2021-03-08 19:03:50 -05:00
parent c5094ed614
commit 05e28a53e8
8 changed files with 875 additions and 1 deletions

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)

View file

@ -85,6 +85,20 @@ static QString ButtonToText(const Common::ParamPackage& param) {
return {}; return {};
} }
if (param.Get("engine", "") == "gcpad") {
if (param.Has("axis")) {
const QString axis_str = QString::fromStdString(param.Get("axis", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
}
if (param.Has("button")) {
const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
return QObject::tr("GC Button %1").arg(button_str);
}
return GetKeyName(param.Get("code", 0));
}
return QObject::tr("[unknown]"); return QObject::tr("[unknown]");
} }
@ -117,6 +131,25 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string
return {}; return {};
} }
if (param.Get("engine", "") == "gcpad") {
if (dir == "modifier") {
return QObject::tr("[unused]");
}
if (dir == "left" || dir == "right") {
const QString axis_x_str = QString::fromStdString(param.Get("axis_x", ""));
return QObject::tr("GC Axis %1").arg(axis_x_str);
}
if (dir == "up" || dir == "down") {
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
return QObject::tr("GC Axis %1").arg(axis_y_str);
}
return {};
}
return QObject::tr("[unknown]"); return QObject::tr("[unknown]");
} }
@ -418,7 +451,8 @@ 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);
} }

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,4 @@ 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_link_libraries(input_common PUBLIC ${LIBUSB_LIBRARIES})

View file

@ -0,0 +1,329 @@
// Copyright 2014 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <chrono>
#include <thread>
#include <libusb.h>
#include "common/logging/log.h"
#include "input_common/gcadapter/gc_adapter.h"
namespace GCAdapter {
/// Used to loop through and assign button in poller
constexpr std::array<PadButton, 12> PadButtonArray{
PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN,
PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R,
PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B,
PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START,
};
Adapter::Adapter() {
if (usb_adapter_handle != nullptr) {
return;
}
LOG_INFO(Input, "GC Adapter Initialization started");
const int init_res = libusb_init(&libusb_ctx);
if (init_res == LIBUSB_SUCCESS) {
Setup();
} else {
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
}
}
GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) {
GCPadStatus pad = {};
const std::size_t offset = 1 + (9 * port);
adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
static constexpr std::array<PadButton, 8> b1_buttons{
PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT,
PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP,
};
static constexpr std::array<PadButton, 4> b2_buttons{
PadButton::PAD_BUTTON_START,
PadButton::PAD_TRIGGER_Z,
PadButton::PAD_TRIGGER_R,
PadButton::PAD_TRIGGER_L,
};
static constexpr std::array<PadAxes, 6> axes{
PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
};
if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) {
// Controller may have been disconnected, recalibrate if reconnected.
get_origin[port] = true;
}
if (adapter_controllers_status[port] != ControllerTypes::None) {
const u8 b1 = adapter_payload[offset + 1];
const u8 b2 = adapter_payload[offset + 2];
for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
if ((b1 & (1U << i)) != 0) {
pad.button |= static_cast<u16>(b1_buttons[i]);
}
}
for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
if ((b2 & (1U << j)) != 0) {
pad.button |= static_cast<u16>(b2_buttons[j]);
}
}
for (PadAxes axis : axes) {
const std::size_t index = static_cast<std::size_t>(axis);
pad.axis_values[index] = adapter_payload[offset + 3 + index];
}
if (get_origin[port]) {
origin_status[port].axis_values = pad.axis_values;
get_origin[port] = false;
}
}
return pad;
}
void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
for (const auto& button : PadButtonArray) {
const u16 button_value = static_cast<u16>(button);
state.buttons.insert_or_assign(button_value, pad.button & button_value);
}
for (size_t i = 0; i < pad.axis_values.size(); ++i) {
state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]);
}
}
void Adapter::Read() {
LOG_DEBUG(Input, "GC Adapter Read() thread started");
int payload_size;
std::array<u8, 37> adapter_payload;
std::array<GCPadStatus, 4> pads;
while (adapter_thread_running) {
libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
sizeof(adapter_payload), &payload_size, 16);
if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) {
LOG_ERROR(Input,
"Error reading payload (size: {}, type: {:02x}) Is the adapter connected?",
payload_size, adapter_payload[0]);
adapter_thread_running = false; // error reading from adapter, stop reading.
break;
}
for (std::size_t port = 0; port < pads.size(); ++port) {
pads[port] = GetPadStatus(port, adapter_payload);
if (DeviceConnected(port) && configuring) {
if (pads[port].button != 0) {
pad_queue[port].Push(pads[port]);
}
// Accounting for a threshold here to ensure an intentional press
for (size_t i = 0; i < pads[port].axis_values.size(); ++i) {
const u8 value = pads[port].axis_values[i];
const u8 origin = origin_status[port].axis_values[i];
if (value > origin + pads[port].THRESHOLD ||
value < origin - pads[port].THRESHOLD) {
pads[port].axis = static_cast<PadAxes>(i);
pads[port].axis_value = pads[port].axis_values[i];
pad_queue[port].Push(pads[port]);
}
}
}
PadToState(pads[port], state[port]);
}
std::this_thread::yield();
}
}
void Adapter::Setup() {
// Initialize all controllers as unplugged
adapter_controllers_status.fill(ControllerTypes::None);
// Initialize all ports to store axis origin values
get_origin.fill(true);
// pointer to list of connected usb devices
libusb_device** devices{};
// populate the list of devices, get the count
const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
if (device_count < 0) {
LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
return;
}
if (devices != nullptr) {
for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) {
if (CheckDeviceAccess(devices[index])) {
// GC Adapter found and accessible, registering it
GetGCEndpoint(devices[index]);
break;
}
}
libusb_free_device_list(devices, 1);
}
}
bool Adapter::CheckDeviceAccess(libusb_device* device) {
libusb_device_descriptor desc;
const int get_descriptor_error = libusb_get_device_descriptor(device, &desc);
if (get_descriptor_error) {
// could not acquire the descriptor, no point in trying to use it.
LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}",
get_descriptor_error);
return false;
}
if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) {
// This isn't the device we are looking for.
return false;
}
const int open_error = libusb_open(device, &usb_adapter_handle);
if (open_error == LIBUSB_ERROR_ACCESS) {
LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.",
desc.idVendor, desc.idProduct);
return false;
}
if (open_error) {
LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error);
return false;
}
int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
if (kernel_driver_error == 1) {
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
kernel_driver_error);
}
}
if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
return false;
}
const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
if (interface_claim_error) {
LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
return false;
}
return true;
}
void Adapter::GetGCEndpoint(libusb_device* device) {
libusb_config_descriptor* config = nullptr;
const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
if (config_descriptor_return != LIBUSB_SUCCESS) {
LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
config_descriptor_return);
return;
}
for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
const libusb_interface* interfaceContainer = &config->interface[ic];
for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
for (u8 e = 0; e < interface->bNumEndpoints; e++) {
const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
input_endpoint = endpoint->bEndpointAddress;
} else {
output_endpoint = endpoint->bEndpointAddress;
}
}
}
}
// This transfer seems to be responsible for clearing the state of the adapter
// Used to clear the "busy" state of when the device is unexpectedly unplugged
unsigned char clear_payload = 0x13;
libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
sizeof(clear_payload), nullptr, 16);
adapter_thread_running = true;
adapter_input_thread = std::thread(&Adapter::Read, this);
}
Adapter::~Adapter() {
Reset();
}
void Adapter::Reset() {
if (adapter_thread_running) {
adapter_thread_running = false;
}
if (adapter_input_thread.joinable()) {
adapter_input_thread.join();
}
adapter_controllers_status.fill(ControllerTypes::None);
get_origin.fill(true);
if (usb_adapter_handle) {
libusb_release_interface(usb_adapter_handle, 1);
libusb_close(usb_adapter_handle);
usb_adapter_handle = nullptr;
}
if (libusb_ctx) {
libusb_exit(libusb_ctx);
}
}
bool Adapter::DeviceConnected(std::size_t port) {
return adapter_controllers_status[port] != ControllerTypes::None;
}
void Adapter::ResetDeviceType(std::size_t port) {
adapter_controllers_status[port] = ControllerTypes::None;
}
void Adapter::BeginConfiguration() {
get_origin.fill(true);
for (auto& pq : pad_queue) {
pq.Clear();
}
configuring = true;
}
void Adapter::EndConfiguration() {
for (auto& pq : pad_queue) {
pq.Clear();
}
configuring = false;
}
std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() {
return pad_queue;
}
const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const {
return pad_queue;
}
std::array<GCState, 4>& Adapter::GetPadState() {
return state;
}
const std::array<GCState, 4>& Adapter::GetPadState() const {
return state;
}
int Adapter::GetOriginValue(int port, int axis) const {
return origin_status[port].axis_values[axis];
}
} // namespace GCAdapter

View file

@ -0,0 +1,132 @@
// 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 GCAdapter {
enum class PadButton {
PAD_BUTTON_LEFT = 0x0001,
PAD_BUTTON_RIGHT = 0x0002,
PAD_BUTTON_DOWN = 0x0004,
PAD_BUTTON_UP = 0x0008,
PAD_TRIGGER_Z = 0x0010,
PAD_TRIGGER_R = 0x0020,
PAD_TRIGGER_L = 0x0040,
PAD_BUTTON_A = 0x0100,
PAD_BUTTON_B = 0x0200,
PAD_BUTTON_X = 0x0400,
PAD_BUTTON_Y = 0x0800,
PAD_BUTTON_START = 0x1000,
// Below is for compatibility with "AxisButton" type
PAD_STICK = 0x2000,
};
extern const std::array<PadButton, 12> PadButtonArray;
enum class PadAxes : u8 {
StickX,
StickY,
SubstickX,
SubstickY,
TriggerLeft,
TriggerRight,
Undefined,
};
struct GCPadStatus {
u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes
static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling
u8 port{};
PadAxes axis{PadAxes::Undefined};
u8 axis_value{255};
};
struct GCState {
std::unordered_map<int, bool> buttons;
std::unordered_map<int, u16> axes;
};
enum class ControllerTypes { None, Wired, Wireless };
class Adapter {
public:
/// Initialize the GC Adapter capture and read sequence
Adapter();
/// Close the adapter read thread and release the adapter
~Adapter();
/// Used for polling
void BeginConfiguration();
void EndConfiguration();
/// Returns true if there is a device connected to port
bool DeviceConnected(std::size_t port);
std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
std::array<GCState, 4>& GetPadState();
const std::array<GCState, 4>& GetPadState() const;
int GetOriginValue(int port, int axis) const;
private:
GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload);
void PadToState(const GCPadStatus& pad, GCState& state);
void Read();
/// Resets status of device connected to port
void ResetDeviceType(std::size_t port);
/// Returns true if we successfully gain access to GC Adapter
bool CheckDeviceAccess(libusb_device* device);
/// Captures GC Adapter endpoint address,
void GetGCEndpoint(libusb_device* device);
/// For shutting down, clear all data, join all threads, release usb
void Reset();
/// For use in initialization, querying devices to find the adapter
void Setup();
libusb_device_handle* usb_adapter_handle = nullptr;
std::thread adapter_input_thread;
bool adapter_thread_running;
libusb_context* libusb_ctx;
u8 input_endpoint = 0;
u8 output_endpoint = 0;
bool configuring = false;
std::array<GCState, 4> state;
std::array<bool, 4> get_origin;
std::array<GCPadStatus, 4> origin_status;
std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
std::array<ControllerTypes, 4> adapter_controllers_status{};
};
} // namespace GCAdapter

View file

@ -0,0 +1,272 @@
// 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 {
class GCButton final : public Input::ButtonDevice {
public:
explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
return gcadapter->GetPadState()[port].buttons.at(button);
}
return false;
}
private:
const int port;
const int button;
GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
public:
explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
GCAdapter::Adapter* adapter)
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
gcadapter(adapter),
origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
bool GetStatus() const override {
if (gcadapter->DeviceConnected(port)) {
const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis);
const float axis_value = (current_axis_value - origin_value) / 128.0f;
if (trigger_if_greater) {
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
return axis_value > threshold;
}
return axis_value < -threshold;
}
return false;
}
private:
const int port;
const int axis;
float threshold;
bool trigger_if_greater;
GCAdapter::Adapter* gcadapter;
const float origin_value;
};
GCButtonFactory::GCButtonFactory(std::shared_ptr<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 int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_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();
for (std::size_t port = 0; port < queue.size(); ++port) {
while (queue[port].Pop(pad)) {
// This while loop will break on the earliest detected button
params.Set("engine", "gcpad");
params.Set("port", static_cast<int>(port));
for (const auto& button : GCAdapter::PadButtonArray) {
const u16 button_value = static_cast<u16>(button);
if (pad.button & button_value) {
params.Set("button", button_value);
break;
}
}
// For Axis button implementation
if (pad.axis != GCAdapter::PadAxes::Undefined) {
params.Set("axis", static_cast<u8>(pad.axis));
params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK));
if (pad.axis_value > 128) {
params.Set("direction", "+");
params.Set("threshold", "0.25");
} else {
params.Set("direction", "-");
params.Set("threshold", "-0.25");
}
break;
}
}
}
return params;
}
void GCButtonFactory::Start() {
polling = true;
adapter->BeginConfiguration();
}
void GCButtonFactory::Stop() {
polling = false;
adapter->EndConfiguration();
}
class GCAnalog final : public Input::AnalogDevice {
public:
GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter,
float range_)
: port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
range(range_) {}
float GetAxis(int axis) const {
if (gcadapter->DeviceConnected(port)) {
std::lock_guard lock{mutex};
const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y;
return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);
}
return 0.0f;
}
std::pair<float, float> GetAnalog(int axis_x, int axis_y) const {
float x = GetAxis(axis_x);
float y = GetAxis(axis_y);
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
if (r > 1.0f) {
r = std::sqrt(r);
x /= r;
y /= r;
}
return {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 int port;
const int axis_x;
const int axis_y;
const float deadzone;
GCAdapter::Adapter* gcadapter;
const float origin_value_x;
const float origin_value_y;
const float range;
mutable std::mutex mutex;
};
/// An analog device factory that creates analog devices from GC Adapter
GCAnalogFactory::GCAnalogFactory(std::shared_ptr<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 int port = params.Get("port", 0);
const int axis_x = params.Get("axis_x", 0);
const int axis_y = params.Get("axis_y", 1);
const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
}
void GCAnalogFactory::Start() {
polling = true;
adapter->BeginConfiguration();
}
void GCAnalogFactory::Stop() {
polling = false;
adapter->EndConfiguration();
}
Common::ParamPackage GCAnalogFactory::GetNextInput() {
GCAdapter::GCPadStatus pad;
auto& queue = adapter->GetPadQueue();
for (std::size_t port = 0; port < queue.size(); ++port) {
while (queue[port].Pop(pad)) {
if (pad.axis == GCAdapter::PadAxes::Undefined ||
std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.05) {
continue;
}
// An analog device needs two axes, so we need to store the axis for later and wait for
// a second input event. The axes also must be from the same joystick.
const u8 axis = static_cast<u8>(pad.axis);
if (analog_x_axis == -1) {
analog_x_axis = axis;
controller_number = static_cast<int>(port);
} else if (analog_y_axis == -1 && analog_x_axis != axis &&
controller_number == static_cast<int>(port)) {
analog_y_axis = axis;
}
}
}
Common::ParamPackage params;
if (analog_x_axis != -1 && analog_y_axis != -1) {
params.Set("engine", "gcpad");
params.Set("port", controller_number);
params.Set("axis_x", analog_x_axis);
params.Set("axis_y", analog_y_axis);
analog_x_axis = -1;
analog_y_axis = -1;
controller_number = -1;
return params;
}
return params;
}
} // namespace InputCommon

View file

@ -0,0 +1,67 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/frontend/input.h"
#include "input_common/gcadapter/gc_adapter.h"
#include "input_common/main.h"
namespace InputCommon {
/**
* A button device factory representing a gcpad. It receives gcpad events and forward them
* to all button devices it created.
*/
class GCButtonFactory final : public Input::Factory<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;
/// 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;
/// 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");
@ -102,6 +116,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;
} }