add input common changes
This commit is contained in:
@ -1,29 +1,49 @@
|
|||||||
add_library(input_common STATIC
|
add_library(input_common STATIC
|
||||||
analog_from_button.cpp
|
drivers/keyboard.cpp
|
||||||
analog_from_button.h
|
drivers/keyboard.h
|
||||||
keyboard.cpp
|
drivers/mouse.cpp
|
||||||
keyboard.h
|
drivers/mouse.h
|
||||||
|
drivers/touch_screen.cpp
|
||||||
|
drivers/touch_screen.h
|
||||||
|
drivers/udp_client.cpp
|
||||||
|
drivers/udp_client.h
|
||||||
|
drivers/virtual_gamepad.cpp
|
||||||
|
drivers/virtual_gamepad.h
|
||||||
|
helpers/stick_from_buttons.cpp
|
||||||
|
helpers/stick_from_buttons.h
|
||||||
|
helpers/touch_from_buttons.cpp
|
||||||
|
helpers/touch_from_buttons.h
|
||||||
|
helpers/udp_protocol.cpp
|
||||||
|
helpers/udp_protocol.h
|
||||||
|
input_engine.cpp
|
||||||
|
input_engine.h
|
||||||
|
input_mapping.cpp
|
||||||
|
input_mapping.h
|
||||||
|
input_poller.cpp
|
||||||
|
input_poller.h
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h
|
main.h
|
||||||
motion_emu.cpp
|
|
||||||
motion_emu.h
|
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
touch_from_button.cpp
|
|
||||||
touch_from_button.h
|
|
||||||
sdl/sdl.cpp
|
|
||||||
sdl/sdl.h
|
|
||||||
udp/client.cpp
|
|
||||||
udp/client.h
|
|
||||||
udp/protocol.cpp
|
|
||||||
udp/protocol.h
|
|
||||||
udp/udp.cpp
|
|
||||||
udp/udp.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(input_common PRIVATE
|
||||||
|
/W4
|
||||||
|
|
||||||
|
/we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
|
||||||
|
/we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
|
||||||
|
/we4800 # Implicit conversion from 'type' to bool. Possible information loss
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
target_compile_options(input_common PRIVATE
|
||||||
|
-Werror=conversion
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (ENABLE_SDL2)
|
if (ENABLE_SDL2)
|
||||||
target_sources(input_common PRIVATE
|
target_sources(input_common PRIVATE
|
||||||
sdl/sdl_impl.cpp
|
drivers/sdl_driver.cpp
|
||||||
sdl/sdl_impl.h
|
drivers/sdl_driver.h
|
||||||
)
|
)
|
||||||
target_link_libraries(input_common PRIVATE SDL2::SDL2)
|
target_link_libraries(input_common PRIVATE SDL2::SDL2)
|
||||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||||
@ -31,14 +51,11 @@ endif()
|
|||||||
|
|
||||||
if (ENABLE_LIBUSB)
|
if (ENABLE_LIBUSB)
|
||||||
target_sources(input_common PRIVATE
|
target_sources(input_common PRIVATE
|
||||||
gcadapter/gc_adapter.cpp
|
drivers/gc_adapter.cpp
|
||||||
gcadapter/gc_adapter.h
|
drivers/gc_adapter.h
|
||||||
gcadapter/gc_poller.cpp
|
|
||||||
gcadapter/gc_poller.h
|
|
||||||
)
|
)
|
||||||
target_include_directories(input_common PRIVATE ${LIBUSB_INCLUDE_DIR})
|
target_link_libraries(input_common PRIVATE libusb::usb)
|
||||||
target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
|
target_compile_definitions(input_common PRIVATE HAVE_LIBUSB)
|
||||||
add_definitions(-DENABLE_GCADAPTER)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
create_target_directory_groups(input_common)
|
create_target_directory_groups(input_common)
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "input_common/analog_from_button.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
class Analog final : public Input::AnalogDevice {
|
|
||||||
public:
|
|
||||||
using Button = std::unique_ptr<Input::ButtonDevice>;
|
|
||||||
|
|
||||||
Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
|
|
||||||
float modifier_scale_)
|
|
||||||
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
|
|
||||||
right(std::move(right_)), modifier(std::move(modifier_)),
|
|
||||||
modifier_scale(modifier_scale_) {}
|
|
||||||
|
|
||||||
std::tuple<float, float> GetStatus() const override {
|
|
||||||
constexpr float SQRT_HALF = 0.707106781f;
|
|
||||||
int x = 0, y = 0;
|
|
||||||
|
|
||||||
if (right->GetStatus())
|
|
||||||
++x;
|
|
||||||
if (left->GetStatus())
|
|
||||||
--x;
|
|
||||||
if (up->GetStatus())
|
|
||||||
++y;
|
|
||||||
if (down->GetStatus())
|
|
||||||
--y;
|
|
||||||
|
|
||||||
float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
|
|
||||||
return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF),
|
|
||||||
y * coef * (x == 0 ? 1.0f : SQRT_HALF));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Button up;
|
|
||||||
Button down;
|
|
||||||
Button left;
|
|
||||||
Button right;
|
|
||||||
Button modifier;
|
|
||||||
float modifier_scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
|
|
||||||
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
|
||||||
auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
|
|
||||||
auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
|
|
||||||
auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
|
|
||||||
auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
|
|
||||||
auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
|
|
||||||
auto modifier_scale = params.Get("modifier_scale", 0.5f);
|
|
||||||
return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
|
|
||||||
std::move(right), std::move(modifier), modifier_scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
546
src/input_common/drivers/gc_adapter.cpp
Normal file
546
src/input_common/drivers/gc_adapter.cpp
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <libusb.h>
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/settings_input.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "input_common/drivers/gc_adapter.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class LibUSBContext {
|
||||||
|
public:
|
||||||
|
explicit LibUSBContext() {
|
||||||
|
init_result = libusb_init(&ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
~LibUSBContext() {
|
||||||
|
libusb_exit(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
LibUSBContext& operator=(const LibUSBContext&) = delete;
|
||||||
|
LibUSBContext(const LibUSBContext&) = delete;
|
||||||
|
|
||||||
|
LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
|
||||||
|
LibUSBContext(LibUSBContext&&) noexcept = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] int InitResult() const noexcept {
|
||||||
|
return init_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] libusb_context* get() noexcept {
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
libusb_context* ctx;
|
||||||
|
int init_result{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class LibUSBDeviceHandle {
|
||||||
|
public:
|
||||||
|
explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
|
||||||
|
handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
~LibUSBDeviceHandle() noexcept {
|
||||||
|
if (handle) {
|
||||||
|
libusb_release_interface(handle, 1);
|
||||||
|
libusb_close(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
|
||||||
|
LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
|
||||||
|
|
||||||
|
LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
|
||||||
|
LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] libusb_device_handle* get() noexcept {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
libusb_device_handle* handle{};
|
||||||
|
};
|
||||||
|
|
||||||
|
GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
if (usb_adapter_handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(Input, "Initialization started");
|
||||||
|
|
||||||
|
libusb_ctx = std::make_unique<LibUSBContext>();
|
||||||
|
const int init_res = libusb_ctx->InitResult();
|
||||||
|
if (init_res == LIBUSB_SUCCESS) {
|
||||||
|
adapter_scan_thread =
|
||||||
|
std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GCAdapter::~GCAdapter() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
|
||||||
|
LOG_DEBUG(Input, "Input thread started");
|
||||||
|
Common::SetCurrentThreadName("GCAdapter");
|
||||||
|
s32 payload_size{};
|
||||||
|
AdapterPayload adapter_payload{};
|
||||||
|
|
||||||
|
adapter_scan_thread = {};
|
||||||
|
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
|
||||||
|
static_cast<s32>(adapter_payload.size()), &payload_size, 16);
|
||||||
|
if (IsPayloadCorrect(adapter_payload, payload_size)) {
|
||||||
|
UpdateControllers(adapter_payload);
|
||||||
|
UpdateVibrations();
|
||||||
|
}
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restart_scan_thread) {
|
||||||
|
adapter_scan_thread =
|
||||||
|
std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
|
||||||
|
restart_scan_thread = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::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, "Timeout, Is the adapter connected?");
|
||||||
|
adapter_input_thread.request_stop();
|
||||||
|
restart_scan_thread = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_error_counter = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
|
||||||
|
if (pads[port].type == pad_type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Device changed reset device and set new type
|
||||||
|
pads[port].axis_origin = {};
|
||||||
|
pads[port].reset_origin_counter = {};
|
||||||
|
pads[port].enable_vibration = {};
|
||||||
|
pads[port].rumble_amplitude = {};
|
||||||
|
pads[port].type = pad_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
|
||||||
|
[[maybe_unused]] 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
|
||||||
|
const bool button_status = (b1 & (1U << i)) != 0;
|
||||||
|
const int button = static_cast<int>(b1_buttons[i]);
|
||||||
|
SetButton(pads[port].identifier, button, button_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
|
||||||
|
const bool button_status = (b2 & (1U << j)) != 0;
|
||||||
|
const int button = static_cast<int>(b2_buttons[j]);
|
||||||
|
SetButton(pads[port].identifier, button, button_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::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].reset_origin_counter <= 18) {
|
||||||
|
if (pads[port].axis_origin[index] != axis_value) {
|
||||||
|
pads[port].reset_origin_counter = 0;
|
||||||
|
}
|
||||||
|
pads[port].axis_origin[index] = axis_value;
|
||||||
|
pads[port].reset_origin_counter++;
|
||||||
|
}
|
||||||
|
const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
|
||||||
|
SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("ScanGCAdapter");
|
||||||
|
usb_adapter_handle = nullptr;
|
||||||
|
pads = {};
|
||||||
|
while (!stop_token.stop_requested() && !Setup()) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::Setup() {
|
||||||
|
constexpr u16 nintendo_vid = 0x057e;
|
||||||
|
constexpr u16 gc_adapter_pid = 0x0337;
|
||||||
|
usb_adapter_handle =
|
||||||
|
std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
|
||||||
|
if (!usb_adapter_handle->get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!CheckDeviceAccess()) {
|
||||||
|
usb_adapter_handle = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
|
||||||
|
|
||||||
|
LOG_INFO(Input, "GC adapter is now connected");
|
||||||
|
// GC Adapter found and accessible, registering it
|
||||||
|
if (GetGCEndpoint(device)) {
|
||||||
|
rumble_enabled = true;
|
||||||
|
input_error_counter = 0;
|
||||||
|
output_error_counter = 0;
|
||||||
|
|
||||||
|
std::size_t port = 0;
|
||||||
|
for (GCController& pad : pads) {
|
||||||
|
pad.identifier = {
|
||||||
|
.guid = Common::UUID{},
|
||||||
|
.port = port++,
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
PreSetController(pad.identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter_input_thread =
|
||||||
|
std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::CheckDeviceAccess() {
|
||||||
|
s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
|
||||||
|
if (kernel_driver_error == 1) {
|
||||||
|
kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 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) {
|
||||||
|
usb_adapter_handle = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
|
||||||
|
if (interface_claim_error) {
|
||||||
|
LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
|
||||||
|
usb_adapter_handle = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This fixes payload problems from offbrand GCAdapters
|
||||||
|
const s32 control_transfer_error =
|
||||||
|
libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
|
||||||
|
if (control_transfer_error < 0) {
|
||||||
|
LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::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->get(), output_endpoint, &clear_payload,
|
||||||
|
sizeof(clear_payload), nullptr, 16);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::VibrationError GCAdapter::SetVibration(
|
||||||
|
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||||
|
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
|
||||||
|
const auto processed_amplitude =
|
||||||
|
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
|
||||||
|
|
||||||
|
pads[identifier.port].rumble_amplitude = processed_amplitude;
|
||||||
|
|
||||||
|
if (!rumble_enabled) {
|
||||||
|
return Common::Input::VibrationError::Disabled;
|
||||||
|
}
|
||||||
|
return Common::Input::VibrationError::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
|
||||||
|
return rumble_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::UpdateVibrations() {
|
||||||
|
// Use 8 states to keep the switching between on/off fast enough for
|
||||||
|
// a human to feel different vibration strenght
|
||||||
|
// More states == more rumble strengths == slower update time
|
||||||
|
constexpr u8 vibration_states = 8;
|
||||||
|
|
||||||
|
vibration_counter = (vibration_counter + 1) % vibration_states;
|
||||||
|
|
||||||
|
for (GCController& pad : pads) {
|
||||||
|
const bool vibrate = pad.rumble_amplitude > vibration_counter;
|
||||||
|
vibration_changed |= vibrate != pad.enable_vibration;
|
||||||
|
pad.enable_vibration = vibrate;
|
||||||
|
}
|
||||||
|
SendVibrations();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::SendVibrations() {
|
||||||
|
if (!rumble_enabled || !vibration_changed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s32 size{};
|
||||||
|
constexpr u8 rumble_command = 0x11;
|
||||||
|
const u8 p1 = pads[0].enable_vibration;
|
||||||
|
const u8 p2 = pads[1].enable_vibration;
|
||||||
|
const u8 p3 = pads[2].enable_vibration;
|
||||||
|
const u8 p4 = pads[3].enable_vibration;
|
||||||
|
std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
|
||||||
|
const int err =
|
||||||
|
libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
|
||||||
|
static_cast<s32>(payload.size()), &size, 16);
|
||||||
|
if (err) {
|
||||||
|
LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
|
||||||
|
if (output_error_counter++ > 5) {
|
||||||
|
LOG_ERROR(Input, "Output timeout, Rumble disabled");
|
||||||
|
rumble_enabled = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output_error_counter = 0;
|
||||||
|
vibration_changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::DeviceConnected(std::size_t port) const {
|
||||||
|
return pads[port].type != ControllerTypes::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GCAdapter::Reset() {
|
||||||
|
adapter_scan_thread = {};
|
||||||
|
adapter_input_thread = {};
|
||||||
|
usb_adapter_handle = nullptr;
|
||||||
|
pads = {};
|
||||||
|
libusb_ctx = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
for (std::size_t port = 0; port < pads.size(); ++port) {
|
||||||
|
if (!DeviceConnected(port)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Common::ParamPackage identifier{};
|
||||||
|
identifier.Set("engine", GetEngineName());
|
||||||
|
identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
|
||||||
|
identifier.Set("port", static_cast<int>(port));
|
||||||
|
devices.emplace_back(identifier);
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
// This list is missing ZL/ZR since those are not considered buttons.
|
||||||
|
// We will add those afterwards
|
||||||
|
// This list also excludes any button that can't be really mapped
|
||||||
|
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
|
||||||
|
switch_to_gcadapter_button = {
|
||||||
|
std::pair{Settings::NativeButton::A, PadButton::ButtonA},
|
||||||
|
{Settings::NativeButton::B, PadButton::ButtonB},
|
||||||
|
{Settings::NativeButton::X, PadButton::ButtonX},
|
||||||
|
{Settings::NativeButton::Y, PadButton::ButtonY},
|
||||||
|
{Settings::NativeButton::Plus, PadButton::ButtonStart},
|
||||||
|
{Settings::NativeButton::DLeft, PadButton::ButtonLeft},
|
||||||
|
{Settings::NativeButton::DUp, PadButton::ButtonUp},
|
||||||
|
{Settings::NativeButton::DRight, PadButton::ButtonRight},
|
||||||
|
{Settings::NativeButton::DDown, PadButton::ButtonDown},
|
||||||
|
{Settings::NativeButton::SL, PadButton::TriggerL},
|
||||||
|
{Settings::NativeButton::SR, PadButton::TriggerR},
|
||||||
|
{Settings::NativeButton::R, PadButton::TriggerZ},
|
||||||
|
};
|
||||||
|
if (!params.Has("port")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping mapping{};
|
||||||
|
for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
|
||||||
|
Common::ParamPackage button_params{};
|
||||||
|
button_params.Set("engine", GetEngineName());
|
||||||
|
button_params.Set("port", params.Get("port", 0));
|
||||||
|
button_params.Set("button", static_cast<int>(gcadapter_button));
|
||||||
|
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the missing bindings for ZL/ZR
|
||||||
|
static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
|
||||||
|
switch_to_gcadapter_axis = {
|
||||||
|
std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
|
||||||
|
{Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
|
||||||
|
};
|
||||||
|
for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
|
||||||
|
Common::ParamPackage button_params{};
|
||||||
|
button_params.Set("engine", GetEngineName());
|
||||||
|
button_params.Set("port", params.Get("port", 0));
|
||||||
|
button_params.Set("button", static_cast<s32>(gcadapter_buton));
|
||||||
|
button_params.Set("axis", static_cast<s32>(gcadapter_axis));
|
||||||
|
button_params.Set("threshold", 0.5f);
|
||||||
|
button_params.Set("range", 1.9f);
|
||||||
|
button_params.Set("direction", "+");
|
||||||
|
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||||
|
}
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("port")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping mapping = {};
|
||||||
|
Common::ParamPackage left_analog_params;
|
||||||
|
left_analog_params.Set("engine", GetEngineName());
|
||||||
|
left_analog_params.Set("port", params.Get("port", 0));
|
||||||
|
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
|
||||||
|
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||||
|
Common::ParamPackage right_analog_params;
|
||||||
|
right_analog_params.Set("engine", GetEngineName());
|
||||||
|
right_analog_params.Set("port", params.Get("port", 0));
|
||||||
|
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
|
||||||
|
right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||||
|
PadButton button = static_cast<PadButton>(params.Get("button", 0));
|
||||||
|
switch (button) {
|
||||||
|
case PadButton::ButtonLeft:
|
||||||
|
return Common::Input::ButtonNames::ButtonLeft;
|
||||||
|
case PadButton::ButtonRight:
|
||||||
|
return Common::Input::ButtonNames::ButtonRight;
|
||||||
|
case PadButton::ButtonDown:
|
||||||
|
return Common::Input::ButtonNames::ButtonDown;
|
||||||
|
case PadButton::ButtonUp:
|
||||||
|
return Common::Input::ButtonNames::ButtonUp;
|
||||||
|
case PadButton::TriggerZ:
|
||||||
|
return Common::Input::ButtonNames::TriggerZ;
|
||||||
|
case PadButton::TriggerR:
|
||||||
|
return Common::Input::ButtonNames::TriggerR;
|
||||||
|
case PadButton::TriggerL:
|
||||||
|
return Common::Input::ButtonNames::TriggerL;
|
||||||
|
case PadButton::ButtonA:
|
||||||
|
return Common::Input::ButtonNames::ButtonA;
|
||||||
|
case PadButton::ButtonB:
|
||||||
|
return Common::Input::ButtonNames::ButtonB;
|
||||||
|
case PadButton::ButtonX:
|
||||||
|
return Common::Input::ButtonNames::ButtonX;
|
||||||
|
case PadButton::ButtonY:
|
||||||
|
return Common::Input::ButtonNames::ButtonY;
|
||||||
|
case PadButton::ButtonStart:
|
||||||
|
return Common::Input::ButtonNames::ButtonStart;
|
||||||
|
default:
|
||||||
|
return Common::Input::ButtonNames::Undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const {
|
||||||
|
if (params.Has("button")) {
|
||||||
|
return GetUIButtonName(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Common::Input::ButtonNames::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GCAdapter::IsStickInverted(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("port")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
|
||||||
|
const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
|
||||||
|
if (x_axis != PadAxes::StickY && x_axis != PadAxes::SubstickY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (y_axis != PadAxes::StickX && y_axis != PadAxes::SubstickX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
137
src/input_common/drivers/gc_adapter.h
Normal file
137
src/input_common/drivers/gc_adapter.h
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2014 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
struct libusb_context;
|
||||||
|
struct libusb_device;
|
||||||
|
struct libusb_device_handle;
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class LibUSBContext;
|
||||||
|
class LibUSBDeviceHandle;
|
||||||
|
|
||||||
|
class GCAdapter : public InputEngine {
|
||||||
|
public:
|
||||||
|
explicit GCAdapter(std::string input_engine_);
|
||||||
|
~GCAdapter() override;
|
||||||
|
|
||||||
|
Common::Input::VibrationError SetVibration(
|
||||||
|
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||||
|
|
||||||
|
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||||
|
|
||||||
|
/// Used for automapping features
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||||
|
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||||
|
|
||||||
|
bool IsStickInverted(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PadAxes : u8 {
|
||||||
|
StickX,
|
||||||
|
StickY,
|
||||||
|
SubstickX,
|
||||||
|
SubstickY,
|
||||||
|
TriggerLeft,
|
||||||
|
TriggerRight,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControllerTypes {
|
||||||
|
None,
|
||||||
|
Wired,
|
||||||
|
Wireless,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GCController {
|
||||||
|
ControllerTypes type = ControllerTypes::None;
|
||||||
|
PadIdentifier identifier{};
|
||||||
|
bool enable_vibration = false;
|
||||||
|
u8 rumble_amplitude{};
|
||||||
|
std::array<u8, 6> axis_origin{};
|
||||||
|
u8 reset_origin_counter{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using AdapterPayload = std::array<u8, 37>;
|
||||||
|
|
||||||
|
void UpdatePadType(std::size_t port, ControllerTypes pad_type);
|
||||||
|
void UpdateControllers(const AdapterPayload& adapter_payload);
|
||||||
|
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
|
||||||
|
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
|
||||||
|
|
||||||
|
void AdapterInputThread(std::stop_token stop_token);
|
||||||
|
|
||||||
|
void AdapterScanThread(std::stop_token stop_token);
|
||||||
|
|
||||||
|
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
|
||||||
|
|
||||||
|
/// For use in initialization, querying devices to find the adapter
|
||||||
|
bool Setup();
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Returns true if there is a device connected to port
|
||||||
|
bool DeviceConnected(std::size_t port) const;
|
||||||
|
|
||||||
|
/// For shutting down, clear all data, join all threads, release usb
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
void UpdateVibrations();
|
||||||
|
|
||||||
|
/// Updates vibration state of all controllers
|
||||||
|
void SendVibrations();
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
|
||||||
|
std::array<GCController, 4> pads;
|
||||||
|
|
||||||
|
std::jthread adapter_input_thread;
|
||||||
|
std::jthread adapter_scan_thread;
|
||||||
|
bool restart_scan_thread{};
|
||||||
|
|
||||||
|
std::unique_ptr<LibUSBContext> libusb_ctx;
|
||||||
|
|
||||||
|
u8 input_endpoint{0};
|
||||||
|
u8 output_endpoint{0};
|
||||||
|
u8 input_error_counter{0};
|
||||||
|
u8 output_error_counter{0};
|
||||||
|
int vibration_counter{0};
|
||||||
|
|
||||||
|
bool rumble_enabled{true};
|
||||||
|
bool vibration_changed{true};
|
||||||
|
};
|
||||||
|
} // namespace InputCommon
|
40
src/input_common/drivers/keyboard.cpp
Normal file
40
src/input_common/drivers/keyboard.cpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "input_common/drivers/keyboard.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
constexpr PadIdentifier key_identifier = {
|
||||||
|
.guid = Common::UUID{},
|
||||||
|
.port = 0,
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
PreSetController(key_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::PressKey(int key_code) {
|
||||||
|
SetButton(key_identifier, key_code, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::ReleaseKey(int key_code) {
|
||||||
|
SetButton(key_identifier, key_code, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keyboard::ReleaseAllKeys() {
|
||||||
|
ResetButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
devices.emplace_back(Common::ParamPackage{
|
||||||
|
{"engine", GetEngineName()},
|
||||||
|
{"display", "Keyboard Only"},
|
||||||
|
});
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
37
src/input_common/drivers/keyboard.h
Normal file
37
src/input_common/drivers/keyboard.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||||
|
* to all button devices it created.
|
||||||
|
*/
|
||||||
|
class Keyboard final : public InputEngine {
|
||||||
|
public:
|
||||||
|
explicit Keyboard(std::string input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to pressed
|
||||||
|
* @param key_code the code of the key to press
|
||||||
|
*/
|
||||||
|
void PressKey(int key_code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to released
|
||||||
|
* @param key_code the code of the key to release
|
||||||
|
*/
|
||||||
|
void ReleaseKey(int key_code);
|
||||||
|
|
||||||
|
/// Sets all keys to the non pressed state
|
||||||
|
void ReleaseAllKeys();
|
||||||
|
|
||||||
|
/// Used for automapping features
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
148
src/input_common/drivers/mouse.cpp
Normal file
148
src/input_common/drivers/mouse.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "input_common/drivers/mouse.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
constexpr int mouse_axis_x = 0;
|
||||||
|
constexpr int mouse_axis_y = 1;
|
||||||
|
constexpr int wheel_axis_x = 2;
|
||||||
|
constexpr int wheel_axis_y = 3;
|
||||||
|
constexpr int motion_wheel_y = 4;
|
||||||
|
constexpr int touch_axis_x = 10;
|
||||||
|
constexpr int touch_axis_y = 11;
|
||||||
|
constexpr PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{},
|
||||||
|
.port = 0,
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
PreSetController(identifier);
|
||||||
|
PreSetAxis(identifier, mouse_axis_x);
|
||||||
|
PreSetAxis(identifier, mouse_axis_y);
|
||||||
|
PreSetAxis(identifier, wheel_axis_x);
|
||||||
|
PreSetAxis(identifier, wheel_axis_y);
|
||||||
|
PreSetAxis(identifier, motion_wheel_y);
|
||||||
|
PreSetAxis(identifier, touch_axis_x);
|
||||||
|
PreSetAxis(identifier, touch_axis_y);
|
||||||
|
update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::UpdateThread(std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("Mouse");
|
||||||
|
constexpr int update_time = 10;
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
SetAxis(identifier, motion_wheel_y, 0.0f);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
|
||||||
|
SetAxis(identifier, touch_axis_x, touch_x);
|
||||||
|
SetAxis(identifier, touch_axis_y, touch_y);
|
||||||
|
|
||||||
|
if (button_pressed) {
|
||||||
|
constexpr float sensitivity = 0.0012f;
|
||||||
|
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
|
||||||
|
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
|
||||||
|
SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
|
||||||
|
SetAxis(identifier, touch_axis_x, touch_x);
|
||||||
|
SetAxis(identifier, touch_axis_y, touch_y);
|
||||||
|
SetButton(identifier, static_cast<int>(button), true);
|
||||||
|
// Set initial analog parameters
|
||||||
|
mouse_origin = {x, y};
|
||||||
|
last_mouse_position = {x, y};
|
||||||
|
button_pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::ReleaseButton(MouseButton button) {
|
||||||
|
SetButton(identifier, static_cast<int>(button), false);
|
||||||
|
button_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::MouseWheelChange(int x, int y) {
|
||||||
|
wheel_position.x += x;
|
||||||
|
wheel_position.y += y;
|
||||||
|
SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x));
|
||||||
|
SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
|
||||||
|
SetAxis(identifier, motion_wheel_y, static_cast<f32>(y) / 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mouse::ReleaseAllButtons() {
|
||||||
|
ResetButtonState();
|
||||||
|
button_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
devices.emplace_back(Common::ParamPackage{
|
||||||
|
{"engine", GetEngineName()},
|
||||||
|
{"display", "Keyboard/Mouse"},
|
||||||
|
});
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping Mouse::GetAnalogMappingForDevice(
|
||||||
|
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||||
|
// Only overwrite different buttons from default
|
||||||
|
AnalogMapping mapping = {};
|
||||||
|
Common::ParamPackage right_analog_params;
|
||||||
|
right_analog_params.Set("engine", GetEngineName());
|
||||||
|
right_analog_params.Set("axis_x", 0);
|
||||||
|
right_analog_params.Set("axis_y", 1);
|
||||||
|
right_analog_params.Set("threshold", 0.5f);
|
||||||
|
right_analog_params.Set("range", 1.0f);
|
||||||
|
right_analog_params.Set("deadzone", 0.0f);
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::CStick, std::move(right_analog_params));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames Mouse::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||||
|
const auto button = static_cast<MouseButton>(params.Get("button", 0));
|
||||||
|
switch (button) {
|
||||||
|
case MouseButton::Left:
|
||||||
|
return Common::Input::ButtonNames::ButtonLeft;
|
||||||
|
case MouseButton::Right:
|
||||||
|
return Common::Input::ButtonNames::ButtonRight;
|
||||||
|
case MouseButton::Wheel:
|
||||||
|
return Common::Input::ButtonNames::ButtonMouseWheel;
|
||||||
|
case MouseButton::Backward:
|
||||||
|
return Common::Input::ButtonNames::ButtonBackward;
|
||||||
|
case MouseButton::Forward:
|
||||||
|
return Common::Input::ButtonNames::ButtonForward;
|
||||||
|
case MouseButton::Task:
|
||||||
|
return Common::Input::ButtonNames::ButtonTask;
|
||||||
|
case MouseButton::Extra:
|
||||||
|
return Common::Input::ButtonNames::ButtonExtra;
|
||||||
|
case MouseButton::Undefined:
|
||||||
|
default:
|
||||||
|
return Common::Input::ButtonNames::Undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const {
|
||||||
|
if (params.Has("button")) {
|
||||||
|
return GetUIButtonName(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
|
||||||
|
return Common::Input::ButtonNames::Engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Common::Input::ButtonNames::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
79
src/input_common/drivers/mouse.h
Normal file
79
src/input_common/drivers/mouse.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
enum class MouseButton {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Wheel,
|
||||||
|
Backward,
|
||||||
|
Forward,
|
||||||
|
Task,
|
||||||
|
Extra,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||||
|
* to all button devices it created.
|
||||||
|
*/
|
||||||
|
class Mouse final : public InputEngine {
|
||||||
|
public:
|
||||||
|
explicit Mouse(std::string input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that mouse has moved.
|
||||||
|
* @param x the x-coordinate of the cursor
|
||||||
|
* @param y the y-coordinate of the cursor
|
||||||
|
* @param center_x the x-coordinate of the middle of the screen
|
||||||
|
* @param center_y the y-coordinate of the middle of the screen
|
||||||
|
*/
|
||||||
|
void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to pressed
|
||||||
|
* @param key_code the code of the key to press
|
||||||
|
*/
|
||||||
|
void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to released
|
||||||
|
* @param key_code the code of the key to release
|
||||||
|
*/
|
||||||
|
void ReleaseButton(MouseButton button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of the mouse wheel
|
||||||
|
* @param x delta movement in the x direction
|
||||||
|
* @param y delta movement in the y direction
|
||||||
|
*/
|
||||||
|
void MouseWheelChange(int x, int y);
|
||||||
|
|
||||||
|
void ReleaseAllButtons();
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateThread(std::stop_token stop_token);
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
Common::Vec2<int> mouse_origin;
|
||||||
|
Common::Vec2<int> last_mouse_position;
|
||||||
|
Common::Vec2<float> last_mouse_change;
|
||||||
|
Common::Vec2<int> wheel_position;
|
||||||
|
bool button_pressed;
|
||||||
|
std::jthread update_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
818
src/input_common/drivers/sdl_driver.cpp
Normal file
818
src/input_common/drivers/sdl_driver.cpp
Normal file
@ -0,0 +1,818 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/math_util.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "common/vector_math.h"
|
||||||
|
#include "input_common/drivers/sdl_driver.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Common::UUID GetGUID(SDL_Joystick* joystick) {
|
||||||
|
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
|
||||||
|
std::array<u8, 16> data{};
|
||||||
|
std::memcpy(data.data(), guid.data, sizeof(data));
|
||||||
|
// Clear controller name crc
|
||||||
|
std::memset(data.data() + 2, 0, sizeof(u16));
|
||||||
|
return Common::UUID{data};
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
static int SDLEventWatcher(void* user_data, SDL_Event* event) {
|
||||||
|
auto* const sdl_state = static_cast<SDLDriver*>(user_data);
|
||||||
|
|
||||||
|
sdl_state->HandleGameControllerEvent(*event);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDLJoystick {
|
||||||
|
public:
|
||||||
|
SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick,
|
||||||
|
SDL_GameController* game_controller)
|
||||||
|
: guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
|
||||||
|
sdl_controller{game_controller, &SDL_GameControllerClose} {
|
||||||
|
EnableMotion();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableMotion() {
|
||||||
|
if (sdl_controller) {
|
||||||
|
SDL_GameController* controller = sdl_controller.get();
|
||||||
|
has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE;
|
||||||
|
has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE;
|
||||||
|
if (has_accel) {
|
||||||
|
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
|
||||||
|
}
|
||||||
|
if (has_gyro) {
|
||||||
|
SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasGyro() const {
|
||||||
|
return has_gyro;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasAccel() const {
|
||||||
|
return has_accel;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateMotion(SDL_ControllerSensorEvent event) {
|
||||||
|
constexpr float gravity_constant = 9.80665f;
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const u64 time_difference = event.timestamp - last_motion_update;
|
||||||
|
last_motion_update = event.timestamp;
|
||||||
|
switch (event.sensor) {
|
||||||
|
case SDL_SENSOR_ACCEL: {
|
||||||
|
motion.accel_x = -event.data[0] / gravity_constant;
|
||||||
|
motion.accel_y = event.data[2] / gravity_constant;
|
||||||
|
motion.accel_z = -event.data[1] / gravity_constant;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_SENSOR_GYRO: {
|
||||||
|
motion.gyro_x = event.data[0] / (Common::PI * 2);
|
||||||
|
motion.gyro_y = -event.data[2] / (Common::PI * 2);
|
||||||
|
motion.gyro_z = event.data[1] / (Common::PI * 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore duplicated timestamps
|
||||||
|
if (time_difference == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
motion.delta_timestamp = time_difference * 1000;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasicMotion& GetMotion() const {
|
||||||
|
return motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pad identifier of the joystick
|
||||||
|
*/
|
||||||
|
const PadIdentifier GetPadIdentifier() const {
|
||||||
|
return {
|
||||||
|
.guid = guid,
|
||||||
|
.port = static_cast<std::size_t>(port),
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guid of the joystick
|
||||||
|
*/
|
||||||
|
const Common::UUID& GetGUID() const {
|
||||||
|
return guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of joystick from the same type that were connected before this joystick
|
||||||
|
*/
|
||||||
|
int GetPort() const {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Joystick* GetSDLJoystick() const {
|
||||||
|
return sdl_joystick.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GameController* GetSDLGameController() const {
|
||||||
|
return sdl_controller.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
|
||||||
|
sdl_joystick.reset(joystick);
|
||||||
|
sdl_controller.reset(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetControllerName() const {
|
||||||
|
if (sdl_controller) {
|
||||||
|
switch (SDL_GameControllerGetType(sdl_controller.get())) {
|
||||||
|
case SDL_CONTROLLER_TYPE_XBOX360:
|
||||||
|
return "Xbox 360 Controller";
|
||||||
|
case SDL_CONTROLLER_TYPE_XBOXONE:
|
||||||
|
return "Xbox One Controller";
|
||||||
|
case SDL_CONTROLLER_TYPE_PS3:
|
||||||
|
return "DualShock 3 Controller";
|
||||||
|
case SDL_CONTROLLER_TYPE_PS4:
|
||||||
|
return "DualShock 4 Controller";
|
||||||
|
case SDL_CONTROLLER_TYPE_PS5:
|
||||||
|
return "DualSense Controller";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto name = SDL_GameControllerName(sdl_controller.get());
|
||||||
|
if (name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sdl_joystick) {
|
||||||
|
const auto name = SDL_JoystickName(sdl_joystick.get());
|
||||||
|
if (name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Common::UUID guid;
|
||||||
|
int port;
|
||||||
|
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
|
||||||
|
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
|
||||||
|
u64 last_motion_update{};
|
||||||
|
bool has_gyro{false};
|
||||||
|
bool has_accel{false};
|
||||||
|
BasicMotion motion;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const Common::UUID& guid, int port) {
|
||||||
|
std::scoped_lock lock{joystick_map_mutex};
|
||||||
|
const auto it = joystick_map.find(guid);
|
||||||
|
|
||||||
|
if (it != joystick_map.end()) {
|
||||||
|
while (it->second.size() <= static_cast<std::size_t>(port)) {
|
||||||
|
auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
|
||||||
|
nullptr, nullptr);
|
||||||
|
it->second.emplace_back(std::move(joystick));
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second[static_cast<std::size_t>(port)];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
|
||||||
|
|
||||||
|
return joystick_map[guid].emplace_back(std::move(joystick));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
|
||||||
|
return GetSDLJoystickByGUID(Common::UUID{guid}, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
|
||||||
|
auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
|
||||||
|
const auto guid = GetGUID(sdl_joystick);
|
||||||
|
|
||||||
|
std::scoped_lock lock{joystick_map_mutex};
|
||||||
|
const auto map_it = joystick_map.find(guid);
|
||||||
|
|
||||||
|
if (map_it == joystick_map.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
|
||||||
|
[&sdl_joystick](const auto& joystick) {
|
||||||
|
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (vec_it == map_it->second.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *vec_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLDriver::InitJoystick(int joystick_index) {
|
||||||
|
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
|
||||||
|
SDL_GameController* sdl_gamecontroller = nullptr;
|
||||||
|
|
||||||
|
if (SDL_IsGameController(joystick_index)) {
|
||||||
|
sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sdl_joystick) {
|
||||||
|
LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto guid = GetGUID(sdl_joystick);
|
||||||
|
|
||||||
|
std::scoped_lock lock{joystick_map_mutex};
|
||||||
|
if (joystick_map.find(guid) == joystick_map.end()) {
|
||||||
|
auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
|
||||||
|
PreSetController(joystick->GetPadIdentifier());
|
||||||
|
joystick->EnableMotion();
|
||||||
|
joystick_map[guid].emplace_back(std::move(joystick));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& joystick_guid_list = joystick_map[guid];
|
||||||
|
const auto joystick_it =
|
||||||
|
std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||||
|
[](const auto& joystick) { return !joystick->GetSDLJoystick(); });
|
||||||
|
|
||||||
|
if (joystick_it != joystick_guid_list.end()) {
|
||||||
|
(*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
|
||||||
|
(*joystick_it)->EnableMotion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int port = static_cast<int>(joystick_guid_list.size());
|
||||||
|
auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
|
||||||
|
PreSetController(joystick->GetPadIdentifier());
|
||||||
|
joystick->EnableMotion();
|
||||||
|
joystick_guid_list.emplace_back(std::move(joystick));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
|
||||||
|
const auto guid = GetGUID(sdl_joystick);
|
||||||
|
|
||||||
|
std::scoped_lock lock{joystick_map_mutex};
|
||||||
|
// This call to guid is safe since the joystick is guaranteed to be in the map
|
||||||
|
const auto& joystick_guid_list = joystick_map[guid];
|
||||||
|
const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
|
||||||
|
[&sdl_joystick](const auto& joystick) {
|
||||||
|
return joystick->GetSDLJoystick() == sdl_joystick;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (joystick_it != joystick_guid_list.end()) {
|
||||||
|
(*joystick_it)->SetSDLJoystick(nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLDriver::PumpEvents() const {
|
||||||
|
if (initialized) {
|
||||||
|
SDL_PumpEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_JOYBUTTONUP: {
|
||||||
|
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||||
|
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||||
|
SetButton(identifier, event.jbutton.button, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_JOYBUTTONDOWN: {
|
||||||
|
if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
|
||||||
|
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||||
|
SetButton(identifier, event.jbutton.button, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_JOYHATMOTION: {
|
||||||
|
if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
|
||||||
|
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||||
|
SetHatButton(identifier, event.jhat.hat, event.jhat.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_JOYAXISMOTION: {
|
||||||
|
if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
|
||||||
|
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||||
|
SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_CONTROLLERSENSORUPDATE: {
|
||||||
|
if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
|
||||||
|
if (joystick->UpdateMotion(event.csensor)) {
|
||||||
|
const PadIdentifier identifier = joystick->GetPadIdentifier();
|
||||||
|
SetMotion(identifier, 0, joystick->GetMotion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_JOYDEVICEREMOVED:
|
||||||
|
LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
|
||||||
|
CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
|
||||||
|
break;
|
||||||
|
case SDL_JOYDEVICEADDED:
|
||||||
|
LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
|
||||||
|
InitJoystick(event.jdevice.which);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLDriver::CloseJoysticks() {
|
||||||
|
std::scoped_lock lock{joystick_map_mutex};
|
||||||
|
joystick_map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
// Prevent SDL from adding undesired axis
|
||||||
|
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
|
||||||
|
|
||||||
|
// Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||||
|
|
||||||
|
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
|
||||||
|
// not a generic one
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||||
|
|
||||||
|
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
|
||||||
|
// driver on Linux.
|
||||||
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
|
||||||
|
|
||||||
|
// If the frontend is going to manage the event loop, then we don't start one here
|
||||||
|
start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
|
||||||
|
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
|
LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AddEventWatch(&SDLEventWatcher, this);
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
// Because the events for joystick connection happens before we have our event watcher added, we
|
||||||
|
// can just open all the joysticks right here
|
||||||
|
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
|
||||||
|
InitJoystick(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLDriver::~SDLDriver() {
|
||||||
|
CloseJoysticks();
|
||||||
|
SDL_DelEventWatch(&SDLEventWatcher, this);
|
||||||
|
|
||||||
|
initialized = false;
|
||||||
|
if (start_thread) {
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
|
||||||
|
for (const auto& [key, value] : joystick_map) {
|
||||||
|
for (const auto& joystick : value) {
|
||||||
|
if (!joystick->GetSDLJoystick()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string name =
|
||||||
|
fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
|
||||||
|
devices.emplace_back(Common::ParamPackage{
|
||||||
|
{"engine", GetEngineName()},
|
||||||
|
{"display", std::move(name)},
|
||||||
|
{"guid", joystick->GetGUID().RawString()},
|
||||||
|
{"port", std::to_string(joystick->GetPort())},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
|
||||||
|
s32 axis, float value) const {
|
||||||
|
Common::ParamPackage params{};
|
||||||
|
params.Set("engine", GetEngineName());
|
||||||
|
params.Set("port", port);
|
||||||
|
params.Set("guid", guid.RawString());
|
||||||
|
params.Set("axis", axis);
|
||||||
|
params.Set("threshold", "0.5");
|
||||||
|
params.Set("invert", value < 0 ? "-" : "+");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
|
||||||
|
s32 button) const {
|
||||||
|
Common::ParamPackage params{};
|
||||||
|
params.Set("engine", GetEngineName());
|
||||||
|
params.Set("port", port);
|
||||||
|
params.Set("guid", guid.RawString());
|
||||||
|
params.Set("button", button);
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, const Common::UUID& guid,
|
||||||
|
s32 hat, u8 value) const {
|
||||||
|
Common::ParamPackage params{};
|
||||||
|
params.Set("engine", GetEngineName());
|
||||||
|
params.Set("port", port);
|
||||||
|
params.Set("guid", guid.RawString());
|
||||||
|
params.Set("hat", hat);
|
||||||
|
params.Set("direction", GetHatButtonName(value));
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& guid) const {
|
||||||
|
Common::ParamPackage params{};
|
||||||
|
params.Set("engine", GetEngineName());
|
||||||
|
params.Set("motion", 0);
|
||||||
|
params.Set("port", port);
|
||||||
|
params.Set("guid", guid.RawString());
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
|
||||||
|
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const {
|
||||||
|
switch (binding.bindType) {
|
||||||
|
case SDL_CONTROLLER_BINDTYPE_NONE:
|
||||||
|
break;
|
||||||
|
case SDL_CONTROLLER_BINDTYPE_AXIS:
|
||||||
|
return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
|
||||||
|
case SDL_CONTROLLER_BINDTYPE_BUTTON:
|
||||||
|
return BuildButtonParamPackageForButton(port, guid, binding.value.button);
|
||||||
|
case SDL_CONTROLLER_BINDTYPE_HAT:
|
||||||
|
return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
|
||||||
|
static_cast<u8>(binding.value.hat.hat_mask));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
|
||||||
|
int axis_y, float offset_x,
|
||||||
|
float offset_y) const {
|
||||||
|
Common::ParamPackage params;
|
||||||
|
params.Set("engine", GetEngineName());
|
||||||
|
params.Set("port", static_cast<int>(identifier.port));
|
||||||
|
params.Set("guid", identifier.guid.RawString());
|
||||||
|
params.Set("axis_x", axis_x);
|
||||||
|
params.Set("axis_y", axis_y);
|
||||||
|
params.Set("offset_x", offset_x);
|
||||||
|
params.Set("offset_y", offset_y);
|
||||||
|
params.Set("invert_x", "+");
|
||||||
|
params.Set("invert_y", "+");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||||
|
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
|
||||||
|
// We will add those afterwards
|
||||||
|
// This list also excludes Screenshot since theres not really a mapping for that
|
||||||
|
ButtonBindings switch_to_sdl_button;
|
||||||
|
|
||||||
|
if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
|
||||||
|
switch_to_sdl_button = GetNintendoButtonBinding(joystick);
|
||||||
|
} else {
|
||||||
|
switch_to_sdl_button = GetDefaultButtonBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the missing bindings for ZL/ZR
|
||||||
|
static constexpr ZButtonBindings switch_to_sdl_axis{{
|
||||||
|
{Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
|
||||||
|
{Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
|
||||||
|
}};
|
||||||
|
|
||||||
|
// Parameters contain two joysticks return dual
|
||||||
|
if (params.Has("guid2")) {
|
||||||
|
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||||
|
|
||||||
|
if (joystick2->GetSDLGameController() != nullptr) {
|
||||||
|
return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
|
||||||
|
switch_to_sdl_axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
|
||||||
|
return {
|
||||||
|
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
|
||||||
|
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
|
||||||
|
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
|
||||||
|
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
|
||||||
|
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||||
|
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{Settings::NativeButton::Start, SDL_CONTROLLER_BUTTON_START},
|
||||||
|
{Settings::NativeButton::Select, SDL_CONTROLLER_BUTTON_BACK},
|
||||||
|
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||||
|
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||||
|
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||||
|
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||||
|
{Settings::NativeButton::ZL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||||
|
{Settings::NativeButton::ZR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonBindings SDLDriver::GetNintendoButtonBinding(
|
||||||
|
const std::shared_ptr<SDLJoystick>& joystick) const {
|
||||||
|
return {
|
||||||
|
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
|
||||||
|
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
|
||||||
|
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
|
||||||
|
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
|
||||||
|
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||||
|
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{Settings::NativeButton::Start, SDL_CONTROLLER_BUTTON_START},
|
||||||
|
{Settings::NativeButton::Select, SDL_CONTROLLER_BUTTON_BACK},
|
||||||
|
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||||
|
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||||
|
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||||
|
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||||
|
{Settings::NativeButton::ZL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||||
|
{Settings::NativeButton::ZR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||||
|
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping SDLDriver::GetSingleControllerMapping(
|
||||||
|
const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
|
||||||
|
const ZButtonBindings& switch_to_sdl_axis) const {
|
||||||
|
ButtonMapping mapping;
|
||||||
|
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
|
||||||
|
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||||
|
}
|
||||||
|
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||||
|
const std::shared_ptr<SDLJoystick>& joystick2,
|
||||||
|
const ButtonBindings& switch_to_sdl_button,
|
||||||
|
const ZButtonBindings& switch_to_sdl_axis) const {
|
||||||
|
ButtonMapping mapping;
|
||||||
|
mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
auto* controller2 = joystick2->GetSDLGameController();
|
||||||
|
|
||||||
|
for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
|
||||||
|
if (IsButtonOnLeftSide(switch_button)) {
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||||
|
}
|
||||||
|
for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
|
||||||
|
if (IsButtonOnLeftSide(switch_button)) {
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
|
||||||
|
mapping.insert_or_assign(
|
||||||
|
switch_button,
|
||||||
|
BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
|
||||||
|
switch (button) {
|
||||||
|
case Settings::NativeButton::DDown:
|
||||||
|
case Settings::NativeButton::DLeft:
|
||||||
|
case Settings::NativeButton::DRight:
|
||||||
|
case Settings::NativeButton::DUp:
|
||||||
|
case Settings::NativeButton::L:
|
||||||
|
case Settings::NativeButton::Start:
|
||||||
|
case Settings::NativeButton::ZL:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||||
|
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping mapping = {};
|
||||||
|
const auto& binding_left_x =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
const auto& binding_left_y =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
if (params.Has("guid2")) {
|
||||||
|
const auto identifier = joystick2->GetPadIdentifier();
|
||||||
|
PreSetController(identifier);
|
||||||
|
PreSetAxis(identifier, binding_left_x.value.axis);
|
||||||
|
PreSetAxis(identifier, binding_left_y.value.axis);
|
||||||
|
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
|
||||||
|
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis);
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::CirclePad,
|
||||||
|
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
|
||||||
|
binding_left_y.value.axis,
|
||||||
|
left_offset_x, left_offset_y));
|
||||||
|
} else {
|
||||||
|
const auto identifier = joystick->GetPadIdentifier();
|
||||||
|
PreSetController(identifier);
|
||||||
|
PreSetAxis(identifier, binding_left_x.value.axis);
|
||||||
|
PreSetAxis(identifier, binding_left_y.value.axis);
|
||||||
|
const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
|
||||||
|
const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis);
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::CirclePad,
|
||||||
|
BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
|
||||||
|
binding_left_y.value.axis,
|
||||||
|
left_offset_x, left_offset_y));
|
||||||
|
}
|
||||||
|
const auto& binding_right_x =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
|
const auto& binding_right_y =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
|
const auto identifier = joystick->GetPadIdentifier();
|
||||||
|
PreSetController(identifier);
|
||||||
|
PreSetAxis(identifier, binding_right_x.value.axis);
|
||||||
|
PreSetAxis(identifier, binding_right_y.value.axis);
|
||||||
|
const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
|
||||||
|
const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis);
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::CStick,
|
||||||
|
BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
|
||||||
|
binding_right_y.value.axis, right_offset_x,
|
||||||
|
right_offset_y));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||||
|
const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionMapping mapping = {};
|
||||||
|
joystick->EnableMotion();
|
||||||
|
|
||||||
|
if (joystick->HasGyro() || joystick->HasAccel()) {
|
||||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
|
||||||
|
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
|
||||||
|
}
|
||||||
|
if (params.Has("guid2")) {
|
||||||
|
joystick2->EnableMotion();
|
||||||
|
if (joystick2->HasGyro() || joystick2->HasAccel()) {
|
||||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
|
||||||
|
BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (joystick->HasGyro() || joystick->HasAccel()) {
|
||||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
|
||||||
|
BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
|
||||||
|
if (params.Has("button")) {
|
||||||
|
// TODO(German77): Find how to substitue the values for real button names
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("hat")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("axis")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("motion")) {
|
||||||
|
return Common::Input::ButtonNames::Engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Common::Input::ButtonNames::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
|
||||||
|
switch (direction_value) {
|
||||||
|
case SDL_HAT_UP:
|
||||||
|
return "up";
|
||||||
|
case SDL_HAT_DOWN:
|
||||||
|
return "down";
|
||||||
|
case SDL_HAT_LEFT:
|
||||||
|
return "left";
|
||||||
|
case SDL_HAT_RIGHT:
|
||||||
|
return "right";
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
|
||||||
|
Uint8 direction;
|
||||||
|
if (direction_name == "up") {
|
||||||
|
direction = SDL_HAT_UP;
|
||||||
|
} else if (direction_name == "down") {
|
||||||
|
direction = SDL_HAT_DOWN;
|
||||||
|
} else if (direction_name == "left") {
|
||||||
|
direction = SDL_HAT_LEFT;
|
||||||
|
} else if (direction_name == "right") {
|
||||||
|
direction = SDL_HAT_RIGHT;
|
||||||
|
} else {
|
||||||
|
direction = 0;
|
||||||
|
}
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
|
||||||
|
if (joystick == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto* controller = joystick->GetSDLGameController();
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& axis_x = params.Get("axis_x", 0);
|
||||||
|
const auto& axis_y = params.Get("axis_y", 0);
|
||||||
|
const auto& binding_left_x =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
const auto& binding_right_x =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
|
const auto& binding_left_y =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
const auto& binding_right_y =
|
||||||
|
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
|
|
||||||
|
if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
117
src/input_common/drivers/sdl_driver.h
Normal file
117
src/input_common/drivers/sdl_driver.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/threadsafe_queue.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
union SDL_Event;
|
||||||
|
using SDL_GameController = struct _SDL_GameController;
|
||||||
|
using SDL_Joystick = struct _SDL_Joystick;
|
||||||
|
using SDL_JoystickID = s32;
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class SDLJoystick;
|
||||||
|
|
||||||
|
using ButtonBindings =
|
||||||
|
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>;
|
||||||
|
using ZButtonBindings =
|
||||||
|
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
|
||||||
|
|
||||||
|
class SDLDriver : public InputEngine {
|
||||||
|
public:
|
||||||
|
/// Initializes and registers SDL device factories
|
||||||
|
explicit SDLDriver(std::string input_engine_);
|
||||||
|
|
||||||
|
/// Unregisters SDL device factories and shut them down.
|
||||||
|
~SDLDriver() override;
|
||||||
|
|
||||||
|
void PumpEvents() const;
|
||||||
|
|
||||||
|
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||||
|
void HandleGameControllerEvent(const SDL_Event& event);
|
||||||
|
|
||||||
|
/// Get the nth joystick with the corresponding GUID
|
||||||
|
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
|
||||||
|
* tie it to a SDLJoystick with the same guid and that port
|
||||||
|
*/
|
||||||
|
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const Common::UUID& guid, int port);
|
||||||
|
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||||
|
|
||||||
|
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||||
|
|
||||||
|
std::string GetHatButtonName(u8 direction_value) const override;
|
||||||
|
u8 GetHatButtonId(const std::string& direction_name) const override;
|
||||||
|
|
||||||
|
bool IsStickInverted(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitJoystick(int joystick_index);
|
||||||
|
void CloseJoystick(SDL_Joystick* sdl_joystick);
|
||||||
|
|
||||||
|
/// Needs to be called before SDL_QuitSubSystem.
|
||||||
|
void CloseJoysticks();
|
||||||
|
|
||||||
|
Common::ParamPackage BuildAnalogParamPackageForButton(int port, const Common::UUID& guid,
|
||||||
|
s32 axis, float value = 0.1f) const;
|
||||||
|
Common::ParamPackage BuildButtonParamPackageForButton(int port, const Common::UUID& guid,
|
||||||
|
s32 button) const;
|
||||||
|
|
||||||
|
Common::ParamPackage BuildHatParamPackageForButton(int port, const Common::UUID& guid, s32 hat,
|
||||||
|
u8 value) const;
|
||||||
|
|
||||||
|
Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const;
|
||||||
|
|
||||||
|
Common::ParamPackage BuildParamPackageForBinding(
|
||||||
|
int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const;
|
||||||
|
|
||||||
|
Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
|
||||||
|
int axis_y, float offset_x,
|
||||||
|
float offset_y) const;
|
||||||
|
|
||||||
|
/// Returns the default button bindings list for generic controllers
|
||||||
|
ButtonBindings GetDefaultButtonBinding() const;
|
||||||
|
|
||||||
|
/// Returns the default button bindings list for nintendo controllers
|
||||||
|
ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
|
||||||
|
|
||||||
|
/// Returns the button mappings from a single controller
|
||||||
|
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||||
|
const ButtonBindings& switch_to_sdl_button,
|
||||||
|
const ZButtonBindings& switch_to_sdl_axis) const;
|
||||||
|
|
||||||
|
/// Returns the button mappings from two different controllers
|
||||||
|
ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||||
|
const std::shared_ptr<SDLJoystick>& joystick2,
|
||||||
|
const ButtonBindings& switch_to_sdl_button,
|
||||||
|
const ZButtonBindings& switch_to_sdl_axis) const;
|
||||||
|
|
||||||
|
/// Returns true if the button is on the left joycon
|
||||||
|
bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
|
||||||
|
|
||||||
|
/// Map of GUID of a list of corresponding virtual Joysticks
|
||||||
|
std::unordered_map<Common::UUID, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
|
||||||
|
std::mutex joystick_map_mutex;
|
||||||
|
|
||||||
|
bool start_thread = false;
|
||||||
|
std::atomic<bool> initialized = false;
|
||||||
|
};
|
||||||
|
} // namespace InputCommon
|
107
src/input_common/drivers/touch_screen.cpp
Normal file
107
src/input_common/drivers/touch_screen.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "input_common/drivers/touch_screen.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
constexpr PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{},
|
||||||
|
.port = 0,
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
PreSetController(identifier);
|
||||||
|
ReleaseAllTouch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::TouchMoved(float x, float y, std::size_t finger_id) {
|
||||||
|
const auto index = GetIndexFromFingerId(finger_id);
|
||||||
|
if (!index) {
|
||||||
|
// Touch doesn't exist handle it as a new one
|
||||||
|
TouchPressed(x, y, finger_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto i = index.value();
|
||||||
|
fingers[i].is_active = true;
|
||||||
|
SetButton(identifier, static_cast<int>(i), true);
|
||||||
|
SetAxis(identifier, static_cast<int>(i * 2), x);
|
||||||
|
SetAxis(identifier, static_cast<int>(i * 2 + 1), y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::TouchPressed(float x, float y, std::size_t finger_id) {
|
||||||
|
if (GetIndexFromFingerId(finger_id)) {
|
||||||
|
// Touch already exist. Just update the data
|
||||||
|
TouchMoved(x, y, finger_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto index = GetNextFreeIndex();
|
||||||
|
if (!index) {
|
||||||
|
// No free entries. Ignore input
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto i = index.value();
|
||||||
|
fingers[i].is_enabled = true;
|
||||||
|
fingers[i].finger_id = finger_id;
|
||||||
|
TouchMoved(x, y, finger_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::TouchReleased(std::size_t finger_id) {
|
||||||
|
const auto index = GetIndexFromFingerId(finger_id);
|
||||||
|
if (!index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto i = index.value();
|
||||||
|
fingers[i].is_enabled = false;
|
||||||
|
SetButton(identifier, static_cast<int>(i), false);
|
||||||
|
SetAxis(identifier, static_cast<int>(i * 2), 0.0f);
|
||||||
|
SetAxis(identifier, static_cast<int>(i * 2 + 1), 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> TouchScreen::GetIndexFromFingerId(std::size_t finger_id) const {
|
||||||
|
for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
|
||||||
|
const auto& finger = fingers[index];
|
||||||
|
if (!finger.is_enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (finger.finger_id == finger_id) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> TouchScreen::GetNextFreeIndex() const {
|
||||||
|
for (std::size_t index = 0; index < MAX_FINGER_COUNT; ++index) {
|
||||||
|
if (!fingers[index].is_enabled) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::ClearActiveFlag() {
|
||||||
|
for (auto& finger : fingers) {
|
||||||
|
finger.is_active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::ReleaseInactiveTouch() {
|
||||||
|
for (const auto& finger : fingers) {
|
||||||
|
if (!finger.is_active) {
|
||||||
|
TouchReleased(finger.finger_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TouchScreen::ReleaseAllTouch() {
|
||||||
|
for (const auto& finger : fingers) {
|
||||||
|
if (finger.is_enabled) {
|
||||||
|
TouchReleased(finger.finger_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
67
src/input_common/drivers/touch_screen.h
Normal file
67
src/input_common/drivers/touch_screen.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A touch device factory representing a touch screen. It receives touch events and forward them
|
||||||
|
* to all touch devices it created.
|
||||||
|
*/
|
||||||
|
class TouchScreen final : public InputEngine {
|
||||||
|
public:
|
||||||
|
explicit TouchScreen(std::string input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that touch has moved and marks this touch point as active
|
||||||
|
* @param x new horizontal position
|
||||||
|
* @param y new vertical position
|
||||||
|
* @param finger_id of the touch point to be updated
|
||||||
|
*/
|
||||||
|
void TouchMoved(float x, float y, std::size_t finger_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals and creates a new touch point with this finger id
|
||||||
|
* @param x starting horizontal position
|
||||||
|
* @param y starting vertical position
|
||||||
|
* @param finger_id to be assigned to the new touch point
|
||||||
|
*/
|
||||||
|
void TouchPressed(float x, float y, std::size_t finger_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals and resets the touch point related to the this finger id
|
||||||
|
* @param finger_id to be released
|
||||||
|
*/
|
||||||
|
void TouchReleased(std::size_t finger_id);
|
||||||
|
|
||||||
|
/// Resets the active flag for each touch point
|
||||||
|
void ClearActiveFlag();
|
||||||
|
|
||||||
|
/// Releases all touch that haven't been marked as active
|
||||||
|
void ReleaseInactiveTouch();
|
||||||
|
|
||||||
|
/// Resets all inputs to their initial value
|
||||||
|
void ReleaseAllTouch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::size_t MAX_FINGER_COUNT = 16;
|
||||||
|
|
||||||
|
struct TouchStatus {
|
||||||
|
std::size_t finger_id{};
|
||||||
|
bool is_enabled{};
|
||||||
|
bool is_active{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;
|
||||||
|
|
||||||
|
std::optional<std::size_t> GetNextFreeIndex() const;
|
||||||
|
|
||||||
|
std::array<TouchStatus, MAX_FINGER_COUNT> fingers{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
648
src/input_common/drivers/udp_client.cpp
Normal file
648
src/input_common/drivers/udp_client.cpp
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "input_common/drivers/udp_client.h"
|
||||||
|
#include "input_common/helpers/udp_protocol.h"
|
||||||
|
|
||||||
|
using boost::asio::ip::udp;
|
||||||
|
|
||||||
|
namespace InputCommon::CemuhookUDP {
|
||||||
|
|
||||||
|
struct SocketCallback {
|
||||||
|
std::function<void(Response::Version)> version;
|
||||||
|
std::function<void(Response::PortInfo)> port_info;
|
||||||
|
std::function<void(Response::PadData)> pad_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Socket {
|
||||||
|
public:
|
||||||
|
using clock = std::chrono::system_clock;
|
||||||
|
|
||||||
|
explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
|
||||||
|
: callback(std::move(callback_)), timer(io_service),
|
||||||
|
socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
|
||||||
|
boost::system::error_code ec{};
|
||||||
|
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
|
||||||
|
if (ec.value() != boost::system::errc::success) {
|
||||||
|
LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
|
||||||
|
ipv4 = boost::asio::ip::address_v4{};
|
||||||
|
}
|
||||||
|
|
||||||
|
send_endpoint = {udp::endpoint(ipv4, port)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
io_service.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loop() {
|
||||||
|
io_service.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartSend(const clock::time_point& from) {
|
||||||
|
timer.expires_at(from + std::chrono::seconds(3));
|
||||||
|
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartReceive() {
|
||||||
|
socket.async_receive_from(
|
||||||
|
boost::asio::buffer(receive_buffer), receive_endpoint,
|
||||||
|
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
|
||||||
|
HandleReceive(error, bytes_transferred);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
u32 GenerateRandomClientId() const {
|
||||||
|
std::random_device device;
|
||||||
|
return device();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
|
||||||
|
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
|
||||||
|
switch (*type) {
|
||||||
|
case Type::Version: {
|
||||||
|
Response::Version version;
|
||||||
|
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
|
||||||
|
callback.version(std::move(version));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type::PortInfo: {
|
||||||
|
Response::PortInfo port_info;
|
||||||
|
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
|
||||||
|
sizeof(Response::PortInfo));
|
||||||
|
callback.port_info(std::move(port_info));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type::PadData: {
|
||||||
|
Response::PadData pad_data;
|
||||||
|
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
|
||||||
|
callback.pad_data(std::move(pad_data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StartReceive();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleSend(const boost::system::error_code&) {
|
||||||
|
boost::system::error_code _ignored{};
|
||||||
|
// Send a request for getting port info for the pad
|
||||||
|
const Request::PortInfo port_info{4, {0, 1, 2, 3}};
|
||||||
|
const auto port_message = Request::Create(port_info, client_id);
|
||||||
|
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
|
||||||
|
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
|
||||||
|
|
||||||
|
// Send a request for getting pad data for the pad
|
||||||
|
const Request::PadData pad_data{
|
||||||
|
Request::RegisterFlags::AllPads,
|
||||||
|
0,
|
||||||
|
EMPTY_MAC_ADDRESS,
|
||||||
|
};
|
||||||
|
const auto pad_message = Request::Create(pad_data, client_id);
|
||||||
|
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
|
||||||
|
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
|
||||||
|
StartSend(timer.expiry());
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketCallback callback;
|
||||||
|
boost::asio::io_service io_service;
|
||||||
|
boost::asio::basic_waitable_timer<clock> timer;
|
||||||
|
udp::socket socket;
|
||||||
|
|
||||||
|
const u32 client_id;
|
||||||
|
|
||||||
|
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
|
||||||
|
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
|
||||||
|
std::array<u8, PORT_INFO_SIZE> send_buffer1;
|
||||||
|
std::array<u8, PAD_DATA_SIZE> send_buffer2;
|
||||||
|
udp::endpoint send_endpoint;
|
||||||
|
|
||||||
|
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
|
||||||
|
udp::endpoint receive_endpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void SocketLoop(Socket* socket) {
|
||||||
|
socket->StartReceive();
|
||||||
|
socket->StartSend(Socket::clock::now());
|
||||||
|
socket->Loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
LOG_INFO(Input, "Udp Initialization started");
|
||||||
|
ReloadSockets();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPClient::~UDPClient() {
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPClient::ClientConnection::ClientConnection() = default;
|
||||||
|
|
||||||
|
UDPClient::ClientConnection::~ClientConnection() = default;
|
||||||
|
|
||||||
|
void UDPClient::ReloadSockets() {
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
|
||||||
|
std::string server_token;
|
||||||
|
std::size_t client = 0;
|
||||||
|
while (std::getline(servers_ss, server_token, ',')) {
|
||||||
|
if (client == MAX_UDP_CLIENTS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::stringstream server_ss(server_token);
|
||||||
|
std::string token;
|
||||||
|
std::getline(server_ss, token, ':');
|
||||||
|
std::string udp_input_address = token;
|
||||||
|
std::getline(server_ss, token, ':');
|
||||||
|
char* temp;
|
||||||
|
const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
|
||||||
|
if (*temp != '\0') {
|
||||||
|
LOG_ERROR(Input, "Port number is not valid {}", token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
|
||||||
|
if (client_number != MAX_UDP_CLIENTS) {
|
||||||
|
LOG_ERROR(Input, "Duplicated UDP servers found");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StartCommunication(client++, udp_input_address, udp_input_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
|
||||||
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
|
if (clients[client].active == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (clients[client].host == host && clients[client].port == port) {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MAX_UDP_CLIENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
|
||||||
|
switch (battery) {
|
||||||
|
case Response::Battery::Dying:
|
||||||
|
return Common::Input::BatteryLevel::Empty;
|
||||||
|
case Response::Battery::Low:
|
||||||
|
return Common::Input::BatteryLevel::Critical;
|
||||||
|
case Response::Battery::Medium:
|
||||||
|
return Common::Input::BatteryLevel::Low;
|
||||||
|
case Response::Battery::High:
|
||||||
|
return Common::Input::BatteryLevel::Medium;
|
||||||
|
case Response::Battery::Full:
|
||||||
|
case Response::Battery::Charged:
|
||||||
|
return Common::Input::BatteryLevel::Full;
|
||||||
|
case Response::Battery::Charging:
|
||||||
|
default:
|
||||||
|
return Common::Input::BatteryLevel::Charging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
|
||||||
|
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
|
||||||
|
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
|
||||||
|
const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
|
||||||
|
|
||||||
|
if (pad_index >= pads.size()) {
|
||||||
|
LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE(Input, "PadData packet received");
|
||||||
|
if (data.packet_counter == pads[pad_index].packet_sequence) {
|
||||||
|
LOG_WARNING(
|
||||||
|
Input,
|
||||||
|
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
||||||
|
pads[pad_index].packet_sequence, data.packet_counter);
|
||||||
|
pads[pad_index].connected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[client].active = 1;
|
||||||
|
pads[pad_index].connected = true;
|
||||||
|
pads[pad_index].packet_sequence = data.packet_counter;
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const auto time_difference = static_cast<u64>(
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
|
||||||
|
.count());
|
||||||
|
pads[pad_index].last_update = now;
|
||||||
|
|
||||||
|
// Gyroscope values are not it the correct scale from better joy.
|
||||||
|
// Dividing by 312 allows us to make one full turn = 1 turn
|
||||||
|
// This must be a configurable valued called sensitivity
|
||||||
|
const float gyro_scale = 1.0f / 312.0f;
|
||||||
|
|
||||||
|
const BasicMotion motion{
|
||||||
|
.gyro_x = data.gyro.pitch * gyro_scale,
|
||||||
|
.gyro_y = data.gyro.roll * gyro_scale,
|
||||||
|
.gyro_z = -data.gyro.yaw * gyro_scale,
|
||||||
|
.accel_x = data.accel.x,
|
||||||
|
.accel_y = -data.accel.z,
|
||||||
|
.accel_z = data.accel.y,
|
||||||
|
.delta_timestamp = time_difference,
|
||||||
|
};
|
||||||
|
const PadIdentifier identifier = GetPadIdentifier(pad_index);
|
||||||
|
SetMotion(identifier, 0, motion);
|
||||||
|
|
||||||
|
for (std::size_t id = 0; id < data.touch.size(); ++id) {
|
||||||
|
const auto touch_pad = data.touch[id];
|
||||||
|
const auto touch_axis_x_id =
|
||||||
|
static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
|
||||||
|
const auto touch_axis_y_id =
|
||||||
|
static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
|
||||||
|
const auto touch_button_id =
|
||||||
|
static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::Touch2);
|
||||||
|
|
||||||
|
// TODO: Use custom calibration per device
|
||||||
|
const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
|
||||||
|
const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
|
||||||
|
const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
|
||||||
|
const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
|
||||||
|
const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
|
||||||
|
|
||||||
|
const f32 x =
|
||||||
|
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
|
||||||
|
static_cast<f32>(max_x - min_x);
|
||||||
|
const f32 y =
|
||||||
|
static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
|
||||||
|
static_cast<f32>(max_y - min_y);
|
||||||
|
|
||||||
|
if (touch_pad.is_active) {
|
||||||
|
SetAxis(identifier, touch_axis_x_id, x);
|
||||||
|
SetAxis(identifier, touch_axis_y_id, y);
|
||||||
|
SetButton(identifier, touch_button_id, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SetAxis(identifier, touch_axis_x_id, 0);
|
||||||
|
SetAxis(identifier, touch_axis_y_id, 0);
|
||||||
|
SetButton(identifier, touch_button_id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
|
||||||
|
(data.left_stick_x - 127.0f) / 127.0f);
|
||||||
|
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
|
||||||
|
(data.left_stick_y - 127.0f) / 127.0f);
|
||||||
|
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
|
||||||
|
(data.right_stick_x - 127.0f) / 127.0f);
|
||||||
|
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
|
||||||
|
(data.right_stick_y - 127.0f) / 127.0f);
|
||||||
|
|
||||||
|
static constexpr std::array<PadButton, 16> buttons{
|
||||||
|
PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options,
|
||||||
|
PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left,
|
||||||
|
PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1,
|
||||||
|
PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < buttons.size(); ++i) {
|
||||||
|
const bool button_status = (data.digital_button & (1U << i)) != 0;
|
||||||
|
const int button = static_cast<int>(buttons[i]);
|
||||||
|
SetButton(identifier, button, button_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetButton(identifier, static_cast<int>(PadButton::Home), data.home != 0);
|
||||||
|
SetButton(identifier, static_cast<int>(PadButton::TouchHardPress), data.touch_hard_press != 0);
|
||||||
|
|
||||||
|
SetBattery(identifier, GetBatteryLevel(data.info.battery));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
|
||||||
|
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
||||||
|
[this](Response::PortInfo info) { OnPortInfo(info); },
|
||||||
|
[this, client](Response::PadData data) { OnPadData(data, client); }};
|
||||||
|
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
||||||
|
clients[client].uuid = GetHostUUID(host);
|
||||||
|
clients[client].host = host;
|
||||||
|
clients[client].port = port;
|
||||||
|
clients[client].active = 0;
|
||||||
|
clients[client].socket = std::make_unique<Socket>(host, port, callback);
|
||||||
|
clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
|
||||||
|
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
|
||||||
|
const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
|
||||||
|
PreSetController(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
|
||||||
|
const std::size_t client = pad_index / PADS_PER_CLIENT;
|
||||||
|
return {
|
||||||
|
.guid = clients[client].uuid,
|
||||||
|
.port = static_cast<std::size_t>(clients[client].port),
|
||||||
|
.pad = pad_index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::UUID UDPClient::GetHostUUID(const std::string& host) const {
|
||||||
|
const auto ip = boost::asio::ip::make_address_v4(host);
|
||||||
|
const auto hex_host = fmt::format("00000000-0000-0000-0000-0000{:06x}", ip.to_uint());
|
||||||
|
return Common::UUID{hex_host};
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDPClient::Reset() {
|
||||||
|
for (auto& client : clients) {
|
||||||
|
if (client.thread.joinable()) {
|
||||||
|
client.active = -1;
|
||||||
|
client.socket->Stop();
|
||||||
|
client.thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
if (!Settings::values.enable_udp_controller) {
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
|
if (clients[client].active != 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
|
||||||
|
const std::size_t pad_index = client * PADS_PER_CLIENT + index;
|
||||||
|
if (!pads[pad_index].connected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto pad_identifier = GetPadIdentifier(pad_index);
|
||||||
|
Common::ParamPackage identifier{};
|
||||||
|
identifier.Set("engine", GetEngineName());
|
||||||
|
identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
|
||||||
|
identifier.Set("guid", pad_identifier.guid.RawString());
|
||||||
|
identifier.Set("port", static_cast<int>(pad_identifier.port));
|
||||||
|
identifier.Set("pad", static_cast<int>(pad_identifier.pad));
|
||||||
|
devices.emplace_back(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
// This list excludes any button that can't be really mapped
|
||||||
|
static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 20>
|
||||||
|
switch_to_dsu_button = {
|
||||||
|
std::pair{Settings::NativeButton::A, PadButton::Circle},
|
||||||
|
{Settings::NativeButton::B, PadButton::Cross},
|
||||||
|
{Settings::NativeButton::X, PadButton::Triangle},
|
||||||
|
{Settings::NativeButton::Y, PadButton::Square},
|
||||||
|
{Settings::NativeButton::Start, PadButton::Options},
|
||||||
|
{Settings::NativeButton::Select, PadButton::Share},
|
||||||
|
{Settings::NativeButton::DLeft, PadButton::Left},
|
||||||
|
{Settings::NativeButton::DUp, PadButton::Up},
|
||||||
|
{Settings::NativeButton::DRight, PadButton::Right},
|
||||||
|
{Settings::NativeButton::DDown, PadButton::Down},
|
||||||
|
{Settings::NativeButton::L, PadButton::L1},
|
||||||
|
{Settings::NativeButton::R, PadButton::R1},
|
||||||
|
{Settings::NativeButton::ZL, PadButton::L2},
|
||||||
|
{Settings::NativeButton::ZR, PadButton::R2},
|
||||||
|
{Settings::NativeButton::ZL, PadButton::L2},
|
||||||
|
{Settings::NativeButton::ZR, PadButton::R2},
|
||||||
|
{Settings::NativeButton::Home, PadButton::Home},
|
||||||
|
};
|
||||||
|
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping mapping{};
|
||||||
|
for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
|
||||||
|
Common::ParamPackage button_params{};
|
||||||
|
button_params.Set("engine", GetEngineName());
|
||||||
|
button_params.Set("guid", params.Get("guid", ""));
|
||||||
|
button_params.Set("port", params.Get("port", 0));
|
||||||
|
button_params.Set("pad", params.Get("pad", 0));
|
||||||
|
button_params.Set("button", static_cast<int>(dsu_button));
|
||||||
|
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping mapping = {};
|
||||||
|
Common::ParamPackage left_analog_params;
|
||||||
|
left_analog_params.Set("engine", GetEngineName());
|
||||||
|
left_analog_params.Set("guid", params.Get("guid", ""));
|
||||||
|
left_analog_params.Set("port", params.Get("port", 0));
|
||||||
|
left_analog_params.Set("pad", params.Get("pad", 0));
|
||||||
|
left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
|
||||||
|
left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||||
|
|
||||||
|
Common::ParamPackage right_analog_params;
|
||||||
|
right_analog_params.Set("engine", GetEngineName());
|
||||||
|
right_analog_params.Set("guid", params.Get("guid", ""));
|
||||||
|
right_analog_params.Set("port", params.Get("port", 0));
|
||||||
|
right_analog_params.Set("pad", params.Get("pad", 0));
|
||||||
|
right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
|
||||||
|
right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
|
||||||
|
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionMapping mapping = {};
|
||||||
|
Common::ParamPackage left_motion_params;
|
||||||
|
left_motion_params.Set("engine", GetEngineName());
|
||||||
|
left_motion_params.Set("guid", params.Get("guid", ""));
|
||||||
|
left_motion_params.Set("port", params.Get("port", 0));
|
||||||
|
left_motion_params.Set("pad", params.Get("pad", 0));
|
||||||
|
left_motion_params.Set("motion", 0);
|
||||||
|
|
||||||
|
Common::ParamPackage right_motion_params;
|
||||||
|
right_motion_params.Set("engine", GetEngineName());
|
||||||
|
right_motion_params.Set("guid", params.Get("guid", ""));
|
||||||
|
right_motion_params.Set("port", params.Get("port", 0));
|
||||||
|
right_motion_params.Set("pad", params.Get("pad", 0));
|
||||||
|
right_motion_params.Set("motion", 0);
|
||||||
|
|
||||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
|
||||||
|
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_motion_params));
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||||
|
PadButton button = static_cast<PadButton>(params.Get("button", 0));
|
||||||
|
switch (button) {
|
||||||
|
case PadButton::Left:
|
||||||
|
return Common::Input::ButtonNames::ButtonLeft;
|
||||||
|
case PadButton::Right:
|
||||||
|
return Common::Input::ButtonNames::ButtonRight;
|
||||||
|
case PadButton::Down:
|
||||||
|
return Common::Input::ButtonNames::ButtonDown;
|
||||||
|
case PadButton::Up:
|
||||||
|
return Common::Input::ButtonNames::ButtonUp;
|
||||||
|
case PadButton::L1:
|
||||||
|
return Common::Input::ButtonNames::L1;
|
||||||
|
case PadButton::L2:
|
||||||
|
return Common::Input::ButtonNames::L2;
|
||||||
|
case PadButton::L3:
|
||||||
|
return Common::Input::ButtonNames::L3;
|
||||||
|
case PadButton::R1:
|
||||||
|
return Common::Input::ButtonNames::R1;
|
||||||
|
case PadButton::R2:
|
||||||
|
return Common::Input::ButtonNames::R2;
|
||||||
|
case PadButton::R3:
|
||||||
|
return Common::Input::ButtonNames::R3;
|
||||||
|
case PadButton::Circle:
|
||||||
|
return Common::Input::ButtonNames::Circle;
|
||||||
|
case PadButton::Cross:
|
||||||
|
return Common::Input::ButtonNames::Cross;
|
||||||
|
case PadButton::Square:
|
||||||
|
return Common::Input::ButtonNames::Square;
|
||||||
|
case PadButton::Triangle:
|
||||||
|
return Common::Input::ButtonNames::Triangle;
|
||||||
|
case PadButton::Share:
|
||||||
|
return Common::Input::ButtonNames::Share;
|
||||||
|
case PadButton::Options:
|
||||||
|
return Common::Input::ButtonNames::Options;
|
||||||
|
case PadButton::Home:
|
||||||
|
return Common::Input::ButtonNames::Home;
|
||||||
|
case PadButton::Touch1:
|
||||||
|
case PadButton::Touch2:
|
||||||
|
case PadButton::TouchHardPress:
|
||||||
|
return Common::Input::ButtonNames::Touch;
|
||||||
|
default:
|
||||||
|
return Common::Input::ButtonNames::Undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const {
|
||||||
|
if (params.Has("button")) {
|
||||||
|
return GetUIButtonName(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis")) {
|
||||||
|
return Common::Input::ButtonNames::Value;
|
||||||
|
}
|
||||||
|
if (params.Has("motion")) {
|
||||||
|
return Common::Input::ButtonNames::Engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Common::Input::ButtonNames::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDPClient::IsStickInverted(const Common::ParamPackage& params) {
|
||||||
|
if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
|
||||||
|
const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
|
||||||
|
if (x_axis != PadAxes::LeftStickY && x_axis != PadAxes::RightStickY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (y_axis != PadAxes::LeftStickX && y_axis != PadAxes::RightStickX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCommunication(const std::string& host, u16 port,
|
||||||
|
const std::function<void()>& success_callback,
|
||||||
|
const std::function<void()>& failure_callback) {
|
||||||
|
std::thread([=] {
|
||||||
|
Common::Event success_event;
|
||||||
|
SocketCallback callback{
|
||||||
|
.version = [](Response::Version) {},
|
||||||
|
.port_info = [](Response::PortInfo) {},
|
||||||
|
.pad_data = [&](Response::PadData) { success_event.Set(); },
|
||||||
|
};
|
||||||
|
Socket socket{host, port, std::move(callback)};
|
||||||
|
std::thread worker_thread{SocketLoop, &socket};
|
||||||
|
const bool result =
|
||||||
|
success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
|
||||||
|
socket.Stop();
|
||||||
|
worker_thread.join();
|
||||||
|
if (result) {
|
||||||
|
success_callback();
|
||||||
|
} else {
|
||||||
|
failure_callback();
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
CalibrationConfigurationJob::CalibrationConfigurationJob(
|
||||||
|
const std::string& host, u16 port, std::function<void(Status)> status_callback,
|
||||||
|
std::function<void(u16, u16, u16, u16)> data_callback) {
|
||||||
|
|
||||||
|
std::thread([=, this] {
|
||||||
|
u16 min_x{UINT16_MAX};
|
||||||
|
u16 min_y{UINT16_MAX};
|
||||||
|
u16 max_x{};
|
||||||
|
u16 max_y{};
|
||||||
|
|
||||||
|
Status current_status{Status::Initialized};
|
||||||
|
SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
|
||||||
|
[&](Response::PadData data) {
|
||||||
|
constexpr u16 CALIBRATION_THRESHOLD = 100;
|
||||||
|
|
||||||
|
if (current_status == Status::Initialized) {
|
||||||
|
// Receiving data means the communication is ready now
|
||||||
|
current_status = Status::Ready;
|
||||||
|
status_callback(current_status);
|
||||||
|
}
|
||||||
|
if (data.touch[0].is_active == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
|
||||||
|
data.touch[0].y);
|
||||||
|
min_x = std::min(min_x, static_cast<u16>(data.touch[0].x));
|
||||||
|
min_y = std::min(min_y, static_cast<u16>(data.touch[0].y));
|
||||||
|
if (current_status == Status::Ready) {
|
||||||
|
// First touch - min data (min_x/min_y)
|
||||||
|
current_status = Status::Stage1Completed;
|
||||||
|
status_callback(current_status);
|
||||||
|
}
|
||||||
|
if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD &&
|
||||||
|
data.touch[0].y - min_y > CALIBRATION_THRESHOLD) {
|
||||||
|
// Set the current position as max value and finishes
|
||||||
|
// configuration
|
||||||
|
max_x = data.touch[0].x;
|
||||||
|
max_y = data.touch[0].y;
|
||||||
|
current_status = Status::Completed;
|
||||||
|
data_callback(min_x, min_y, max_x, max_y);
|
||||||
|
status_callback(current_status);
|
||||||
|
|
||||||
|
complete_event.Set();
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
Socket socket{host, port, std::move(callback)};
|
||||||
|
std::thread worker_thread{SocketLoop, &socket};
|
||||||
|
complete_event.Wait();
|
||||||
|
socket.Stop();
|
||||||
|
worker_thread.join();
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalibrationConfigurationJob::Stop() {
|
||||||
|
complete_event.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon::CemuhookUDP
|
192
src/input_common/drivers/udp_client.h
Normal file
192
src/input_common/drivers/udp_client.h
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon::CemuhookUDP {
|
||||||
|
|
||||||
|
class Socket;
|
||||||
|
|
||||||
|
namespace Response {
|
||||||
|
enum class Battery : u8;
|
||||||
|
struct PadData;
|
||||||
|
struct PortInfo;
|
||||||
|
struct TouchPad;
|
||||||
|
struct Version;
|
||||||
|
} // namespace Response
|
||||||
|
|
||||||
|
enum class PadTouch {
|
||||||
|
Click,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UDPPadStatus {
|
||||||
|
std::string host{"127.0.0.1"};
|
||||||
|
u16 port{26760};
|
||||||
|
std::size_t pad_index{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeviceStatus {
|
||||||
|
std::mutex update_mutex;
|
||||||
|
|
||||||
|
// calibration data for scaling the device's touch area to 3ds
|
||||||
|
struct CalibrationData {
|
||||||
|
u16 min_x{};
|
||||||
|
u16 min_y{};
|
||||||
|
u16 max_x{};
|
||||||
|
u16 max_y{};
|
||||||
|
};
|
||||||
|
std::optional<CalibrationData> touch_calibration;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
||||||
|
* to all button devices it created.
|
||||||
|
*/
|
||||||
|
class UDPClient final : public InputEngine {
|
||||||
|
public:
|
||||||
|
explicit UDPClient(std::string input_engine_);
|
||||||
|
~UDPClient() override;
|
||||||
|
|
||||||
|
void ReloadSockets();
|
||||||
|
|
||||||
|
/// Used for automapping features
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||||
|
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
|
||||||
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||||
|
|
||||||
|
bool IsStickInverted(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class PadButton {
|
||||||
|
Undefined = 0x0000,
|
||||||
|
Share = 0x0001,
|
||||||
|
L3 = 0x0002,
|
||||||
|
R3 = 0x0004,
|
||||||
|
Options = 0x0008,
|
||||||
|
Up = 0x0010,
|
||||||
|
Right = 0x0020,
|
||||||
|
Down = 0x0040,
|
||||||
|
Left = 0x0080,
|
||||||
|
L2 = 0x0100,
|
||||||
|
R2 = 0x0200,
|
||||||
|
L1 = 0x0400,
|
||||||
|
R1 = 0x0800,
|
||||||
|
Triangle = 0x1000,
|
||||||
|
Circle = 0x2000,
|
||||||
|
Cross = 0x4000,
|
||||||
|
Square = 0x8000,
|
||||||
|
Touch1 = 0x10000,
|
||||||
|
Touch2 = 0x20000,
|
||||||
|
Home = 0x40000,
|
||||||
|
TouchHardPress = 0x80000,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PadAxes : u8 {
|
||||||
|
LeftStickX,
|
||||||
|
LeftStickY,
|
||||||
|
RightStickX,
|
||||||
|
RightStickY,
|
||||||
|
AnalogLeft,
|
||||||
|
AnalogDown,
|
||||||
|
AnalogRight,
|
||||||
|
AnalogUp,
|
||||||
|
AnalogSquare,
|
||||||
|
AnalogCross,
|
||||||
|
AnalogCircle,
|
||||||
|
AnalogTriangle,
|
||||||
|
AnalogR1,
|
||||||
|
AnalogL1,
|
||||||
|
AnalogR2,
|
||||||
|
AnalogL3,
|
||||||
|
AnalogR3,
|
||||||
|
Touch1X,
|
||||||
|
Touch1Y,
|
||||||
|
Touch2X,
|
||||||
|
Touch2Y,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PadData {
|
||||||
|
std::size_t pad_index{};
|
||||||
|
bool connected{};
|
||||||
|
DeviceStatus status;
|
||||||
|
u64 packet_sequence{};
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientConnection {
|
||||||
|
ClientConnection();
|
||||||
|
~ClientConnection();
|
||||||
|
Common::UUID uuid{"00000000-0000-0000-0000-00007F000001"};
|
||||||
|
std::string host{"127.0.0.1"};
|
||||||
|
u16 port{26760};
|
||||||
|
s8 active{-1};
|
||||||
|
std::unique_ptr<Socket> socket;
|
||||||
|
std::thread thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For shutting down, clear all data, join all threads, release usb
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
// Translates configuration to client number
|
||||||
|
std::size_t GetClientNumber(std::string_view host, u16 port) const;
|
||||||
|
|
||||||
|
// Translates UDP battery level to input engine battery level
|
||||||
|
Common::Input::BatteryLevel GetBatteryLevel(Response::Battery battery) const;
|
||||||
|
|
||||||
|
void OnVersion(Response::Version);
|
||||||
|
void OnPortInfo(Response::PortInfo);
|
||||||
|
void OnPadData(Response::PadData, std::size_t client);
|
||||||
|
void StartCommunication(std::size_t client, const std::string& host, u16 port);
|
||||||
|
PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
|
||||||
|
Common::UUID GetHostUUID(const std::string& host) const;
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
// Allocate clients for 8 udp servers
|
||||||
|
static constexpr std::size_t MAX_UDP_CLIENTS = 8;
|
||||||
|
static constexpr std::size_t PADS_PER_CLIENT = 4;
|
||||||
|
std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
|
||||||
|
std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An async job allowing configuration of the touchpad calibration.
|
||||||
|
class CalibrationConfigurationJob {
|
||||||
|
public:
|
||||||
|
enum class Status {
|
||||||
|
Initialized,
|
||||||
|
Ready,
|
||||||
|
Stage1Completed,
|
||||||
|
Completed,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Constructs and starts the job with the specified parameter.
|
||||||
|
*
|
||||||
|
* @param status_callback Callback for job status updates
|
||||||
|
* @param data_callback Called when calibration data is ready
|
||||||
|
*/
|
||||||
|
explicit CalibrationConfigurationJob(const std::string& host, u16 port,
|
||||||
|
std::function<void(Status)> status_callback,
|
||||||
|
std::function<void(u16, u16, u16, u16)> data_callback);
|
||||||
|
~CalibrationConfigurationJob();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Common::Event complete_event;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TestCommunication(const std::string& host, u16 port,
|
||||||
|
const std::function<void()>& success_callback,
|
||||||
|
const std::function<void()>& failure_callback);
|
||||||
|
|
||||||
|
} // namespace InputCommon::CemuhookUDP
|
75
src/input_common/drivers/virtual_gamepad.cpp
Normal file
75
src/input_common/drivers/virtual_gamepad.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "input_common/drivers/virtual_gamepad.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
constexpr std::size_t PlayerIndexCount = 10;
|
||||||
|
|
||||||
|
VirtualGamepad::VirtualGamepad(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
||||||
|
for (std::size_t i = 0; i < PlayerIndexCount; i++) {
|
||||||
|
PreSetController(GetIdentifier(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualGamepad::SetButtonState(std::size_t player_index, int button_id, bool value) {
|
||||||
|
if (player_index > PlayerIndexCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto identifier = GetIdentifier(player_index);
|
||||||
|
SetButton(identifier, button_id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualGamepad::SetButtonState(std::size_t player_index, VirtualButton button_id, bool value) {
|
||||||
|
SetButtonState(player_index, static_cast<int>(button_id), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualGamepad::SetStickPosition(std::size_t player_index, int axis_id, float x_value,
|
||||||
|
float y_value) {
|
||||||
|
if (player_index > PlayerIndexCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto identifier = GetIdentifier(player_index);
|
||||||
|
SetAxis(identifier, axis_id * 2, x_value);
|
||||||
|
SetAxis(identifier, (axis_id * 2) + 1, y_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
|
||||||
|
float y_value) {
|
||||||
|
SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VirtualGamepad::ResetControllers() {
|
||||||
|
for (std::size_t i = 0; i < PlayerIndexCount; i++) {
|
||||||
|
SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f);
|
||||||
|
SetStickPosition(i, VirtualStick::Right, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
SetButtonState(i, VirtualButton::ButtonA, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonB, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonX, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonY, false);
|
||||||
|
SetButtonState(i, VirtualButton::StickL, false);
|
||||||
|
SetButtonState(i, VirtualButton::StickR, false);
|
||||||
|
SetButtonState(i, VirtualButton::TriggerL, false);
|
||||||
|
SetButtonState(i, VirtualButton::TriggerR, false);
|
||||||
|
SetButtonState(i, VirtualButton::TriggerZL, false);
|
||||||
|
SetButtonState(i, VirtualButton::TriggerZR, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonStart, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonSelect, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonLeft, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonUp, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonRight, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonDown, false);
|
||||||
|
SetButtonState(i, VirtualButton::ButtonHome, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PadIdentifier VirtualGamepad::GetIdentifier(std::size_t player_index) const {
|
||||||
|
return {
|
||||||
|
.guid = Common::UUID{},
|
||||||
|
.port = player_index,
|
||||||
|
.pad = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
70
src/input_common/drivers/virtual_gamepad.h
Normal file
70
src/input_common/drivers/virtual_gamepad.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A virtual controller that is always assigned to the game input
|
||||||
|
*/
|
||||||
|
class VirtualGamepad final : public InputEngine {
|
||||||
|
public:
|
||||||
|
enum class VirtualButton {
|
||||||
|
ButtonA,
|
||||||
|
ButtonB,
|
||||||
|
ButtonX,
|
||||||
|
ButtonY,
|
||||||
|
StickL,
|
||||||
|
StickR,
|
||||||
|
TriggerL,
|
||||||
|
TriggerR,
|
||||||
|
TriggerZL,
|
||||||
|
TriggerZR,
|
||||||
|
ButtonStart,
|
||||||
|
ButtonSelect,
|
||||||
|
ButtonLeft,
|
||||||
|
ButtonUp,
|
||||||
|
ButtonRight,
|
||||||
|
ButtonDown,
|
||||||
|
ButtonHome,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class VirtualStick {
|
||||||
|
Left = 0,
|
||||||
|
Right = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit VirtualGamepad(std::string input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to pressed
|
||||||
|
* @param player_index the player number that will take this action
|
||||||
|
* @param button_id the id of the button
|
||||||
|
* @param value indicates if the button is pressed or not
|
||||||
|
*/
|
||||||
|
void SetButtonState(std::size_t player_index, int button_id, bool value);
|
||||||
|
void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all buttons bound with the key to released
|
||||||
|
* @param player_index the player number that will take this action
|
||||||
|
* @param axis_id the id of the axis to move
|
||||||
|
* @param x_value the position of the stick in the x axis
|
||||||
|
* @param y_value the position of the stick in the y axis
|
||||||
|
*/
|
||||||
|
void SetStickPosition(std::size_t player_index, int axis_id, float x_value, float y_value);
|
||||||
|
void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
|
||||||
|
float y_value);
|
||||||
|
|
||||||
|
/// Restores all inputs into the neutral position
|
||||||
|
void ResetControllers();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Returns the correct identifier corresponding to the player index
|
||||||
|
PadIdentifier GetIdentifier(std::size_t player_index) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -1,385 +0,0 @@
|
|||||||
// Copyright 2014 Dolphin Emulator Project
|
|
||||||
// Licensed under GPLv2+
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#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"
|
|
||||||
|
|
||||||
// Workaround for older libusb versions not having libusb_init_context.
|
|
||||||
// libusb_init is deprecated and causes a compile error in newer versions.
|
|
||||||
#if !defined(LIBUSB_API_VERSION) || (LIBUSB_API_VERSION < 0x0100010A)
|
|
||||||
#define libusb_init_context(a, b, c) libusb_init(a)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace GCAdapter {
|
|
||||||
|
|
||||||
Adapter::Adapter() {
|
|
||||||
if (usb_adapter_handle != nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const int init_res = libusb_init_context(&libusb_ctx, nullptr, 0);
|
|
||||||
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
|
|
@ -1,153 +0,0 @@
|
|||||||
// 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 <vector>
|
|
||||||
#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
|
|
@ -1,323 +0,0 @@
|
|||||||
// 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
|
|
@ -1,70 +0,0 @@
|
|||||||
// Copyright 2020 yuzu Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include "common/settings.h"
|
|
||||||
#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;
|
|
||||||
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
|
|
334
src/input_common/helpers/stick_from_buttons.cpp
Normal file
334
src/input_common/helpers/stick_from_buttons.cpp
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include "common/math_util.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "input_common/helpers/stick_from_buttons.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class Stick final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
using Button = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
|
||||||
|
Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
|
||||||
|
float modifier_scale_, float modifier_angle_)
|
||||||
|
: up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
|
||||||
|
right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
|
||||||
|
modifier_angle(modifier_angle_) {
|
||||||
|
up->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateUpButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
down->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateDownButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
left->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateLeftButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
right->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateRightButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
modifier->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateModButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
last_x_axis_value = 0.0f;
|
||||||
|
last_y_axis_value = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAngleGreater(float old_angle, float new_angle) const {
|
||||||
|
constexpr float TAU = Common::PI * 2.0f;
|
||||||
|
// Use wider angle to ease the transition.
|
||||||
|
constexpr float aperture = TAU * 0.15f;
|
||||||
|
const float top_limit = new_angle + aperture;
|
||||||
|
return (old_angle > new_angle && old_angle <= top_limit) ||
|
||||||
|
(old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAngleSmaller(float old_angle, float new_angle) const {
|
||||||
|
constexpr float TAU = Common::PI * 2.0f;
|
||||||
|
// Use wider angle to ease the transition.
|
||||||
|
constexpr float aperture = TAU * 0.15f;
|
||||||
|
const float bottom_limit = new_angle - aperture;
|
||||||
|
return (old_angle >= bottom_limit && old_angle < new_angle) ||
|
||||||
|
(old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
|
||||||
|
constexpr float TAU = Common::PI * 2.0f;
|
||||||
|
float new_angle = angle;
|
||||||
|
|
||||||
|
auto time_difference = static_cast<float>(
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
|
||||||
|
time_difference /= 1000.0f * 1000.0f;
|
||||||
|
if (time_difference > 0.5f) {
|
||||||
|
time_difference = 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAngleGreater(new_angle, goal_angle)) {
|
||||||
|
new_angle -= modifier_angle * time_difference;
|
||||||
|
if (new_angle < 0) {
|
||||||
|
new_angle += TAU;
|
||||||
|
}
|
||||||
|
if (!IsAngleGreater(new_angle, goal_angle)) {
|
||||||
|
return goal_angle;
|
||||||
|
}
|
||||||
|
} else if (IsAngleSmaller(new_angle, goal_angle)) {
|
||||||
|
new_angle += modifier_angle * time_difference;
|
||||||
|
if (new_angle >= TAU) {
|
||||||
|
new_angle -= TAU;
|
||||||
|
}
|
||||||
|
if (!IsAngleSmaller(new_angle, goal_angle)) {
|
||||||
|
return goal_angle;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return goal_angle;
|
||||||
|
}
|
||||||
|
return new_angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetGoalAngle(bool r, bool l, bool u, bool d) {
|
||||||
|
// Move to the right
|
||||||
|
if (r && !u && !d) {
|
||||||
|
goal_angle = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the upper right
|
||||||
|
if (r && u && !d) {
|
||||||
|
goal_angle = Common::PI * 0.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move up
|
||||||
|
if (u && !l && !r) {
|
||||||
|
goal_angle = Common::PI * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the upper left
|
||||||
|
if (l && u && !d) {
|
||||||
|
goal_angle = Common::PI * 0.75f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the left
|
||||||
|
if (l && !u && !d) {
|
||||||
|
goal_angle = Common::PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the bottom left
|
||||||
|
if (l && !u && d) {
|
||||||
|
goal_angle = Common::PI * 1.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move down
|
||||||
|
if (d && !l && !r) {
|
||||||
|
goal_angle = Common::PI * 1.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the bottom right
|
||||||
|
if (r && !u && d) {
|
||||||
|
goal_angle = Common::PI * 1.75f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
up_status = button_callback.button_status.value;
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
down_status = button_callback.button_status.value;
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
left_status = button_callback.button_status.value;
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
right_status = button_callback.button_status.value;
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
const auto& new_status = button_callback.button_status;
|
||||||
|
const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value;
|
||||||
|
modifier_status.toggle = new_status.toggle;
|
||||||
|
|
||||||
|
// Update button status with current
|
||||||
|
if (!modifier_status.toggle) {
|
||||||
|
modifier_status.locked = false;
|
||||||
|
if (modifier_status.value != new_button_value) {
|
||||||
|
modifier_status.value = new_button_value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Toggle button and lock status
|
||||||
|
if (new_button_value && !modifier_status.locked) {
|
||||||
|
modifier_status.locked = true;
|
||||||
|
modifier_status.value = !modifier_status.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock button ready for next press
|
||||||
|
if (!new_button_value && modifier_status.locked) {
|
||||||
|
modifier_status.locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatus() {
|
||||||
|
const float coef = modifier_status.value ? modifier_scale : 1.0f;
|
||||||
|
|
||||||
|
bool r = right_status;
|
||||||
|
bool l = left_status;
|
||||||
|
bool u = up_status;
|
||||||
|
bool d = down_status;
|
||||||
|
|
||||||
|
// Eliminate contradictory movements
|
||||||
|
if (r && l) {
|
||||||
|
r = false;
|
||||||
|
l = false;
|
||||||
|
}
|
||||||
|
if (u && d) {
|
||||||
|
u = false;
|
||||||
|
d = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move if a key is pressed
|
||||||
|
if (r || l || u || d) {
|
||||||
|
amplitude = coef;
|
||||||
|
} else {
|
||||||
|
amplitude = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
const auto time_difference = static_cast<u64>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
|
||||||
|
|
||||||
|
if (time_difference < 10) {
|
||||||
|
// Disable analog mode if inputs are too fast
|
||||||
|
SetGoalAngle(r, l, u, d);
|
||||||
|
angle = goal_angle;
|
||||||
|
} else {
|
||||||
|
angle = GetAngle(now);
|
||||||
|
SetGoalAngle(r, l, u, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_update = now;
|
||||||
|
Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Stick,
|
||||||
|
.stick_status = GetStatus(),
|
||||||
|
};
|
||||||
|
last_x_axis_value = status.stick_status.x.raw_value;
|
||||||
|
last_y_axis_value = status.stick_status.y.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
up->ForceUpdate();
|
||||||
|
down->ForceUpdate();
|
||||||
|
left->ForceUpdate();
|
||||||
|
right->ForceUpdate();
|
||||||
|
modifier->ForceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoftUpdate() override {
|
||||||
|
Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Stick,
|
||||||
|
.stick_status = GetStatus(),
|
||||||
|
};
|
||||||
|
if (last_x_axis_value == status.stick_status.x.raw_value &&
|
||||||
|
last_y_axis_value == status.stick_status.y.raw_value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_x_axis_value = status.stick_status.x.raw_value;
|
||||||
|
last_y_axis_value = status.stick_status.y.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::StickStatus GetStatus() const {
|
||||||
|
Common::Input::StickStatus status{};
|
||||||
|
status.x.properties = properties;
|
||||||
|
status.y.properties = properties;
|
||||||
|
constexpr float SQRT_HALF = 0.707106781f;
|
||||||
|
int x = 0, y = 0;
|
||||||
|
if (right_status) {
|
||||||
|
++x;
|
||||||
|
}
|
||||||
|
if (left_status) {
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
if (up_status) {
|
||||||
|
++y;
|
||||||
|
}
|
||||||
|
if (down_status) {
|
||||||
|
--y;
|
||||||
|
}
|
||||||
|
const float coef = modifier_status.value ? modifier_scale : 1.0f;
|
||||||
|
status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
|
||||||
|
status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr Common::Input::AnalogProperties properties{
|
||||||
|
.deadzone = 0.0f,
|
||||||
|
.range = 1.0f,
|
||||||
|
.threshold = 0.5f,
|
||||||
|
.offset = 0.0f,
|
||||||
|
.inverted = false,
|
||||||
|
.toggle = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Button up;
|
||||||
|
Button down;
|
||||||
|
Button left;
|
||||||
|
Button right;
|
||||||
|
Button modifier;
|
||||||
|
float modifier_scale{};
|
||||||
|
float modifier_angle{};
|
||||||
|
float angle{};
|
||||||
|
float goal_angle{};
|
||||||
|
float amplitude{};
|
||||||
|
bool up_status{};
|
||||||
|
bool down_status{};
|
||||||
|
bool left_status{};
|
||||||
|
bool right_status{};
|
||||||
|
float last_x_axis_value{};
|
||||||
|
float last_y_axis_value{};
|
||||||
|
Common::Input::ButtonStatus modifier_status{};
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||||
|
auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine));
|
||||||
|
auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine));
|
||||||
|
auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine));
|
||||||
|
auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine));
|
||||||
|
auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine));
|
||||||
|
auto modifier_scale = params.Get("modifier_scale", 0.5f);
|
||||||
|
auto modifier_angle = params.Get("modifier_angle", 5.5f);
|
||||||
|
return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
|
||||||
|
std::move(right), std::move(modifier), modifier_scale,
|
||||||
|
modifier_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
12
src/input_common/analog_from_button.h → src/input_common/helpers/stick_from_buttons.h
Executable file → Normal file
12
src/input_common/analog_from_button.h → src/input_common/helpers/stick_from_buttons.h
Executable file → Normal file
@ -1,11 +1,9 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include "common/input.h"
|
||||||
#include "core/frontend/input.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace InputCommon {
|
||||||
|
|
||||||
@ -13,7 +11,7 @@ namespace InputCommon {
|
|||||||
* An analog device factory that takes direction button devices and combines them into a analog
|
* An analog device factory that takes direction button devices and combines them into a analog
|
||||||
* device.
|
* device.
|
||||||
*/
|
*/
|
||||||
class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
|
class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Creates an analog device from direction button devices
|
* Creates an analog device from direction button devices
|
||||||
@ -25,7 +23,7 @@ public:
|
|||||||
* - "modifier": a serialized ParamPackage for creating a button device as the modifier
|
* - "modifier": a serialized ParamPackage for creating a button device as the modifier
|
||||||
* - "modifier_scale": a float for the multiplier the modifier gives to the position
|
* - "modifier_scale": a float for the multiplier the modifier gives to the position
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace InputCommon
|
} // namespace InputCommon
|
86
src/input_common/helpers/touch_from_buttons.cpp
Normal file
86
src/input_common/helpers/touch_from_buttons.cpp
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "input_common/helpers/touch_from_buttons.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class TouchFromButtonDevice final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
using Button = std::unique_ptr<Common::Input::InputDevice>;
|
||||||
|
TouchFromButtonDevice(Button button_, float x_, float y_)
|
||||||
|
: button(std::move(button_)), x(x_), y(y_) {
|
||||||
|
last_button_value = false;
|
||||||
|
button->SetCallback({
|
||||||
|
.on_change =
|
||||||
|
[this](const Common::Input::CallbackStatus& callback_) {
|
||||||
|
UpdateButtonStatus(callback_);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
button->ForceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
button->ForceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::TouchStatus GetStatus(bool pressed) const {
|
||||||
|
const Common::Input::ButtonStatus button_status{
|
||||||
|
.value = pressed,
|
||||||
|
};
|
||||||
|
Common::Input::TouchStatus status{
|
||||||
|
.pressed = button_status,
|
||||||
|
.x = {},
|
||||||
|
.y = {},
|
||||||
|
};
|
||||||
|
status.x.properties = properties;
|
||||||
|
status.y.properties = properties;
|
||||||
|
|
||||||
|
if (!pressed) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.x.raw_value = x;
|
||||||
|
status.y.raw_value = y;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateButtonStatus(const Common::Input::CallbackStatus& button_callback) {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Touch,
|
||||||
|
.touch_status = GetStatus(button_callback.button_status.value),
|
||||||
|
};
|
||||||
|
if (last_button_value != button_callback.button_status.value) {
|
||||||
|
last_button_value = button_callback.button_status.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr Common::Input::AnalogProperties properties{
|
||||||
|
.deadzone = 0.0f,
|
||||||
|
.range = 1.0f,
|
||||||
|
.threshold = 0.5f,
|
||||||
|
.offset = 0.0f,
|
||||||
|
.inverted = false,
|
||||||
|
.toggle = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Button button;
|
||||||
|
bool last_button_value;
|
||||||
|
const float x;
|
||||||
|
const float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||||
|
auto button = Common::Input::CreateInputDeviceFromString(params.Get("button", null_engine));
|
||||||
|
const float x = params.Get("x", 0.0f) / 1280.0f;
|
||||||
|
const float y = params.Get("y", 0.0f) / 720.0f;
|
||||||
|
return std::make_unique<TouchFromButtonDevice>(std::move(button), x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
21
src/input_common/helpers/touch_from_buttons.h
Normal file
21
src/input_common/helpers/touch_from_buttons.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/input.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A touch device factory that takes a list of button devices and combines them into a touch device.
|
||||||
|
*/
|
||||||
|
class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Creates a touch device from a list of button devices
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
@ -1,11 +1,10 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "input_common/udp/protocol.h"
|
#include "input_common/helpers/udp_protocol.h"
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
namespace InputCommon::CemuhookUDP {
|
||||||
|
|
@ -1,14 +1,23 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <boost/crc.hpp>
|
#include <boost/crc.hpp>
|
||||||
#include "common/bit_field.h"
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
namespace InputCommon::CemuhookUDP {
|
||||||
@ -52,12 +61,30 @@ struct Message {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
constexpr Type GetMessageType();
|
constexpr Type GetMessageType();
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Message<T> CreateMessage(const u32 magic, const T data, const u32 sender_id) {
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
Header header{
|
||||||
|
magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType<T>(),
|
||||||
|
};
|
||||||
|
Message<T> message{header, data};
|
||||||
|
crc.process_bytes(&message, sizeof(Message<T>));
|
||||||
|
message.header.crc = crc.checksum();
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Request {
|
namespace Request {
|
||||||
|
|
||||||
|
enum RegisterFlags : u8 {
|
||||||
|
AllPads,
|
||||||
|
PadID,
|
||||||
|
PadMACAdddress,
|
||||||
|
};
|
||||||
|
|
||||||
struct Version {};
|
struct Version {};
|
||||||
/**
|
/**
|
||||||
* Requests the server to send information about what controllers are plugged into the ports
|
* Requests the server to send information about what controllers are plugged into the ports
|
||||||
* In citra's case, we only have one controller, so for simplicity's sake, we can just send a
|
* In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a
|
||||||
* request explicitly for the first controller port and leave it at that. In the future it would be
|
* request explicitly for the first controller port and leave it at that. In the future it would be
|
||||||
* nice to make this configurable
|
* nice to make this configurable
|
||||||
*/
|
*/
|
||||||
@ -75,13 +102,8 @@ static_assert(std::is_trivially_copyable_v<PortInfo>,
|
|||||||
* timeout seems to be 5 seconds.
|
* timeout seems to be 5 seconds.
|
||||||
*/
|
*/
|
||||||
struct PadData {
|
struct PadData {
|
||||||
enum class Flags : u8 {
|
|
||||||
AllPorts,
|
|
||||||
Id,
|
|
||||||
Mac,
|
|
||||||
};
|
|
||||||
/// Determines which method will be used as a look up for the controller
|
/// Determines which method will be used as a look up for the controller
|
||||||
Flags flags{};
|
RegisterFlags flags{};
|
||||||
/// Index of the port of the controller to retrieve data about
|
/// Index of the port of the controller to retrieve data about
|
||||||
u8 port_id{};
|
u8 port_id{};
|
||||||
/// Mac address of the controller to retrieve data about
|
/// Mac address of the controller to retrieve data about
|
||||||
@ -93,24 +115,47 @@ static_assert(std::is_trivially_copyable_v<PadData>,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a message with the proper header data that can be sent to the server.
|
* Creates a message with the proper header data that can be sent to the server.
|
||||||
* @param T data Request body to send
|
* @param data Request body to send
|
||||||
* @param client_id ID of the udp client (usually not checked on the server)
|
* @param client_id ID of the udp client (usually not checked on the server)
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Message<T> Create(const T data, const u32 client_id = 0) {
|
Message<T> Create(const T data, const u32 client_id = 0) {
|
||||||
boost::crc_32_type crc;
|
return CreateMessage(CLIENT_MAGIC, data, client_id);
|
||||||
Header header{
|
|
||||||
CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
|
|
||||||
};
|
|
||||||
Message<T> message{header, data};
|
|
||||||
crc.process_bytes(&message, sizeof(Message<T>));
|
|
||||||
message.header.crc = crc.checksum();
|
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
} // namespace Request
|
} // namespace Request
|
||||||
|
|
||||||
namespace Response {
|
namespace Response {
|
||||||
|
|
||||||
|
enum class ConnectionType : u8 {
|
||||||
|
None,
|
||||||
|
Usb,
|
||||||
|
Bluetooth,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class State : u8 {
|
||||||
|
Disconnected,
|
||||||
|
Reserved,
|
||||||
|
Connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Model : u8 {
|
||||||
|
None,
|
||||||
|
PartialGyro,
|
||||||
|
FullGyro,
|
||||||
|
Generic,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Battery : u8 {
|
||||||
|
None = 0x00,
|
||||||
|
Dying = 0x01,
|
||||||
|
Low = 0x02,
|
||||||
|
Medium = 0x03,
|
||||||
|
High = 0x04,
|
||||||
|
Full = 0x05,
|
||||||
|
Charging = 0xEE,
|
||||||
|
Charged = 0xEF,
|
||||||
|
};
|
||||||
|
|
||||||
struct Version {
|
struct Version {
|
||||||
u16_le version{};
|
u16_le version{};
|
||||||
};
|
};
|
||||||
@ -120,17 +165,25 @@ static_assert(std::is_trivially_copyable_v<Version>,
|
|||||||
|
|
||||||
struct PortInfo {
|
struct PortInfo {
|
||||||
u8 id{};
|
u8 id{};
|
||||||
u8 state{};
|
State state{};
|
||||||
u8 model{};
|
Model model{};
|
||||||
u8 connection_type{};
|
ConnectionType connection_type{};
|
||||||
MacAddress mac;
|
MacAddress mac;
|
||||||
u8 battery{};
|
Battery battery{};
|
||||||
u8 is_pad_active{};
|
u8 is_pad_active{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
|
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
|
||||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||||
"UDP Response PortInfo is not trivially copyable");
|
"UDP Response PortInfo is not trivially copyable");
|
||||||
|
|
||||||
|
struct TouchPad {
|
||||||
|
u8 is_active{};
|
||||||
|
u8 id{};
|
||||||
|
u16_le x{};
|
||||||
|
u16_le y{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct PadData {
|
struct PadData {
|
||||||
PortInfo info{};
|
PortInfo info{};
|
||||||
@ -167,26 +220,21 @@ struct PadData {
|
|||||||
u8 right_stick_y{};
|
u8 right_stick_y{};
|
||||||
|
|
||||||
struct AnalogButton {
|
struct AnalogButton {
|
||||||
u8 button_8{};
|
u8 button_dpad_left_analog{};
|
||||||
u8 button_7{};
|
u8 button_dpad_down_analog{};
|
||||||
u8 button_6{};
|
u8 button_dpad_right_analog{};
|
||||||
u8 button_5{};
|
u8 button_dpad_up_analog{};
|
||||||
u8 button_12{};
|
u8 button_square_analog{};
|
||||||
u8 button_11{};
|
u8 button_cross_analog{};
|
||||||
u8 button_10{};
|
u8 button_circle_analog{};
|
||||||
u8 button_9{};
|
u8 button_triangle_analog{};
|
||||||
u8 button_16{};
|
u8 button_r1_analog{};
|
||||||
u8 button_15{};
|
u8 button_l1_analog{};
|
||||||
u8 button_14{};
|
u8 trigger_r2{};
|
||||||
u8 button_13{};
|
u8 trigger_l2{};
|
||||||
} analog_button;
|
} analog_button;
|
||||||
|
|
||||||
struct TouchPad {
|
std::array<TouchPad, 2> touch;
|
||||||
u8 is_active{};
|
|
||||||
u8 id{};
|
|
||||||
u16_le x{};
|
|
||||||
u16_le y{};
|
|
||||||
} touch_1, touch_2;
|
|
||||||
|
|
||||||
u64_le motion_timestamp;
|
u64_le motion_timestamp;
|
||||||
|
|
||||||
@ -213,7 +261,6 @@ static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
|
|||||||
|
|
||||||
static_assert(sizeof(PadData::AnalogButton) == 12,
|
static_assert(sizeof(PadData::AnalogButton) == 12,
|
||||||
"UDP Response AnalogButton struct has wrong size ");
|
"UDP Response AnalogButton struct has wrong size ");
|
||||||
static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
|
|
||||||
static_assert(sizeof(PadData::Accelerometer) == 12,
|
static_assert(sizeof(PadData::Accelerometer) == 12,
|
||||||
"UDP Response Accelerometer struct has wrong size ");
|
"UDP Response Accelerometer struct has wrong size ");
|
||||||
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
|
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
|
331
src/input_common/input_engine.cpp
Normal file
331
src/input_common/input_engine.cpp
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
void InputEngine::PreSetController(const PadIdentifier& identifier) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
controller_list.try_emplace(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
controller.buttons.try_emplace(button, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
controller.hat_buttons.try_emplace(button, u8{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
controller.axes.try_emplace(axis, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
controller.motions.try_emplace(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
if (!configuring) {
|
||||||
|
controller.buttons.insert_or_assign(button, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TriggerOnButtonChange(identifier, button, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
if (!configuring) {
|
||||||
|
controller.hat_buttons.insert_or_assign(button, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TriggerOnHatButtonChange(identifier, button, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
if (!configuring) {
|
||||||
|
controller.axes.insert_or_assign(axis, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TriggerOnAxisChange(identifier, axis, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
ControllerData& controller = controller_list.at(identifier);
|
||||||
|
if (!configuring) {
|
||||||
|
controller.motions.insert_or_assign(motion, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TriggerOnMotionChange(identifier, motion, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const auto controller_iter = controller_list.find(identifier);
|
||||||
|
if (controller_iter == controller_list.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||||
|
identifier.pad, identifier.port);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ControllerData& controller = controller_iter->second;
|
||||||
|
const auto button_iter = controller.buttons.find(button);
|
||||||
|
if (button_iter == controller.buttons.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid button {}", button);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return button_iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const auto controller_iter = controller_list.find(identifier);
|
||||||
|
if (controller_iter == controller_list.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||||
|
identifier.pad, identifier.port);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ControllerData& controller = controller_iter->second;
|
||||||
|
const auto hat_iter = controller.hat_buttons.find(button);
|
||||||
|
if (hat_iter == controller.hat_buttons.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid hat button {}", button);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (hat_iter->second & direction) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const auto controller_iter = controller_list.find(identifier);
|
||||||
|
if (controller_iter == controller_list.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||||
|
identifier.pad, identifier.port);
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const ControllerData& controller = controller_iter->second;
|
||||||
|
const auto axis_iter = controller.axes.find(axis);
|
||||||
|
if (axis_iter == controller.axes.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid axis {}", axis);
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
return axis_iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
|
||||||
|
std::scoped_lock lock{mutex};
|
||||||
|
const auto controller_iter = controller_list.find(identifier);
|
||||||
|
if (controller_iter == controller_list.cend()) {
|
||||||
|
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
|
||||||
|
identifier.pad, identifier.port);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const ControllerData& controller = controller_iter->second;
|
||||||
|
return controller.motions.at(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::ResetButtonState() {
|
||||||
|
for (const auto& controller : controller_list) {
|
||||||
|
for (const auto& button : controller.second.buttons) {
|
||||||
|
SetButton(controller.first, button.first, false);
|
||||||
|
}
|
||||||
|
for (const auto& button : controller.second.hat_buttons) {
|
||||||
|
SetHatButton(controller.first, button.first, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::ResetAnalogState() {
|
||||||
|
for (const auto& controller : controller_list) {
|
||||||
|
for (const auto& axis : controller.second.axes) {
|
||||||
|
SetAxis(controller.first, axis.first, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const InputIdentifier& poller = poller_pair.second;
|
||||||
|
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (poller.callback.on_change) {
|
||||||
|
poller.callback.on_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configuring || !mapping_callback.on_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreSetButton(identifier, button);
|
||||||
|
if (value == GetButton(identifier, button)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mapping_callback.on_data(MappingData{
|
||||||
|
.engine = GetEngineName(),
|
||||||
|
.pad = identifier,
|
||||||
|
.type = EngineInputType::Button,
|
||||||
|
.index = button,
|
||||||
|
.button_value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const InputIdentifier& poller = poller_pair.second;
|
||||||
|
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (poller.callback.on_change) {
|
||||||
|
poller.callback.on_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configuring || !mapping_callback.on_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (std::size_t index = 1; index < 0xff; index <<= 1) {
|
||||||
|
bool button_value = (value & index) != 0;
|
||||||
|
if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mapping_callback.on_data(MappingData{
|
||||||
|
.engine = GetEngineName(),
|
||||||
|
.pad = identifier,
|
||||||
|
.type = EngineInputType::HatButton,
|
||||||
|
.index = button,
|
||||||
|
.hat_name = GetHatButtonName(static_cast<u8>(index)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const InputIdentifier& poller = poller_pair.second;
|
||||||
|
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (poller.callback.on_change) {
|
||||||
|
poller.callback.on_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configuring || !mapping_callback.on_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mapping_callback.on_data(MappingData{
|
||||||
|
.engine = GetEngineName(),
|
||||||
|
.pad = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis,
|
||||||
|
.axis_value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||||
|
const BasicMotion& value) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
for (const auto& poller_pair : callback_list) {
|
||||||
|
const InputIdentifier& poller = poller_pair.second;
|
||||||
|
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (poller.callback.on_change) {
|
||||||
|
poller.callback.on_change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!configuring || !mapping_callback.on_data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool is_active = false;
|
||||||
|
if (std::abs(value.accel_x) > 1.5f || std::abs(value.accel_y) > 1.5f ||
|
||||||
|
std::abs(value.accel_z) > 1.5f) {
|
||||||
|
is_active = true;
|
||||||
|
}
|
||||||
|
if (std::abs(value.gyro_x) > 0.6f || std::abs(value.gyro_y) > 0.6f ||
|
||||||
|
std::abs(value.gyro_z) > 0.6f) {
|
||||||
|
is_active = true;
|
||||||
|
}
|
||||||
|
if (!is_active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mapping_callback.on_data(MappingData{
|
||||||
|
.engine = GetEngineName(),
|
||||||
|
.pad = identifier,
|
||||||
|
.type = EngineInputType::Motion,
|
||||||
|
.index = motion,
|
||||||
|
.motion_value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
|
||||||
|
const PadIdentifier& identifier, EngineInputType type,
|
||||||
|
int index) const {
|
||||||
|
if (input_identifier.type != type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (input_identifier.index != index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (input_identifier.identifier != identifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::BeginConfiguration() {
|
||||||
|
configuring = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::EndConfiguration() {
|
||||||
|
configuring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& InputEngine::GetEngineName() const {
|
||||||
|
return input_engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
int InputEngine::SetCallback(InputIdentifier input_identifier) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
callback_list.insert_or_assign(last_callback_key, std::move(input_identifier));
|
||||||
|
return last_callback_key++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::SetMappingCallback(MappingCallback callback) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
mapping_callback = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputEngine::DeleteCallback(int key) {
|
||||||
|
std::scoped_lock lock{mutex_callback};
|
||||||
|
const auto& iterator = callback_list.find(key);
|
||||||
|
if (iterator == callback_list.end()) {
|
||||||
|
LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback_list.erase(iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
207
src/input_common/input_engine.h
Normal file
207
src/input_common/input_engine.h
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/input.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/uuid.h"
|
||||||
|
#include "input_common/main.h"
|
||||||
|
|
||||||
|
// Pad Identifier of data source
|
||||||
|
struct PadIdentifier {
|
||||||
|
Common::UUID guid{};
|
||||||
|
std::size_t port{};
|
||||||
|
std::size_t pad{};
|
||||||
|
|
||||||
|
friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic motion data containing data from the sensors and a timestamp in microseconds
|
||||||
|
struct BasicMotion {
|
||||||
|
float gyro_x{};
|
||||||
|
float gyro_y{};
|
||||||
|
float gyro_z{};
|
||||||
|
float accel_x{};
|
||||||
|
float accel_y{};
|
||||||
|
float accel_z{};
|
||||||
|
u64 delta_timestamp{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Types of input that are stored in the engine
|
||||||
|
enum class EngineInputType {
|
||||||
|
None,
|
||||||
|
Analog,
|
||||||
|
Button,
|
||||||
|
HatButton,
|
||||||
|
Motion,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
// Hash used to create lists from PadIdentifier data
|
||||||
|
template <>
|
||||||
|
struct hash<PadIdentifier> {
|
||||||
|
size_t operator()(const PadIdentifier& pad_id) const noexcept {
|
||||||
|
u64 hash_value = pad_id.guid.Hash();
|
||||||
|
hash_value ^= (static_cast<u64>(pad_id.port) << 32);
|
||||||
|
hash_value ^= static_cast<u64>(pad_id.pad);
|
||||||
|
return static_cast<size_t>(hash_value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
// Data from the engine and device needed for creating a ParamPackage
|
||||||
|
struct MappingData {
|
||||||
|
std::string engine{};
|
||||||
|
PadIdentifier pad{};
|
||||||
|
EngineInputType type{};
|
||||||
|
int index{};
|
||||||
|
bool button_value{};
|
||||||
|
std::string hat_name{};
|
||||||
|
f32 axis_value{};
|
||||||
|
BasicMotion motion_value{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triggered if data changed on the controller
|
||||||
|
struct UpdateCallback {
|
||||||
|
std::function<void()> on_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triggered if data changed on the controller and the engine is on configuring mode
|
||||||
|
struct MappingCallback {
|
||||||
|
std::function<void(const MappingData&)> on_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Input Identifier of data source
|
||||||
|
struct InputIdentifier {
|
||||||
|
PadIdentifier identifier;
|
||||||
|
EngineInputType type;
|
||||||
|
int index;
|
||||||
|
UpdateCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputEngine {
|
||||||
|
public:
|
||||||
|
explicit InputEngine(std::string input_engine_) : input_engine{std::move(input_engine_)} {}
|
||||||
|
|
||||||
|
virtual ~InputEngine() = default;
|
||||||
|
|
||||||
|
// Enable configuring mode for mapping
|
||||||
|
void BeginConfiguration();
|
||||||
|
|
||||||
|
// Disable configuring mode for mapping
|
||||||
|
void EndConfiguration();
|
||||||
|
|
||||||
|
// Sets polling mode to a controller
|
||||||
|
virtual Common::Input::PollingError SetPollingMode(
|
||||||
|
[[maybe_unused]] const PadIdentifier& identifier,
|
||||||
|
[[maybe_unused]] const Common::Input::PollingMode polling_mode) {
|
||||||
|
return Common::Input::PollingError::NotSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the engine name
|
||||||
|
[[nodiscard]] const std::string& GetEngineName() const;
|
||||||
|
|
||||||
|
/// Used for automapping features
|
||||||
|
virtual std::vector<Common::ParamPackage> GetInputDevices() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the button mappings for the given device
|
||||||
|
virtual ButtonMapping GetButtonMappingForDevice(
|
||||||
|
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the analog mappings for the given device
|
||||||
|
virtual AnalogMapping GetAnalogMappingForDevice(
|
||||||
|
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the motion mappings for the given device
|
||||||
|
virtual MotionMapping GetMotionMappingForDevice(
|
||||||
|
[[maybe_unused]] const Common::ParamPackage& params) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the name of the given input.
|
||||||
|
virtual Common::Input::ButtonNames GetUIName(
|
||||||
|
[[maybe_unused]] const Common::ParamPackage& params) const {
|
||||||
|
return Common::Input::ButtonNames::Engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the index number of the given hat button direction
|
||||||
|
virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if axis of a stick aren't mapped in the correct direction
|
||||||
|
virtual bool IsStickInverted([[maybe_unused]] const Common::ParamPackage& params) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreSetController(const PadIdentifier& identifier);
|
||||||
|
void PreSetButton(const PadIdentifier& identifier, int button);
|
||||||
|
void PreSetHatButton(const PadIdentifier& identifier, int button);
|
||||||
|
void PreSetAxis(const PadIdentifier& identifier, int axis);
|
||||||
|
void PreSetMotion(const PadIdentifier& identifier, int motion);
|
||||||
|
void ResetButtonState();
|
||||||
|
void ResetAnalogState();
|
||||||
|
|
||||||
|
bool GetButton(const PadIdentifier& identifier, int button) const;
|
||||||
|
bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
|
||||||
|
f32 GetAxis(const PadIdentifier& identifier, int axis) const;
|
||||||
|
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
|
||||||
|
|
||||||
|
int SetCallback(InputIdentifier input_identifier);
|
||||||
|
void SetMappingCallback(MappingCallback callback);
|
||||||
|
void DeleteCallback(int key);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SetButton(const PadIdentifier& identifier, int button, bool value);
|
||||||
|
void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
|
||||||
|
void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
|
||||||
|
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
|
||||||
|
|
||||||
|
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ControllerData {
|
||||||
|
std::unordered_map<int, bool> buttons;
|
||||||
|
std::unordered_map<int, u8> hat_buttons;
|
||||||
|
std::unordered_map<int, float> axes;
|
||||||
|
std::unordered_map<int, BasicMotion> motions;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
|
||||||
|
void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
|
||||||
|
void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
|
||||||
|
void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
|
||||||
|
const BasicMotion& value);
|
||||||
|
|
||||||
|
bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
|
||||||
|
const PadIdentifier& identifier, EngineInputType type,
|
||||||
|
int index) const;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
mutable std::mutex mutex_callback;
|
||||||
|
bool configuring{false};
|
||||||
|
const std::string input_engine;
|
||||||
|
int last_callback_key = 0;
|
||||||
|
std::unordered_map<PadIdentifier, ControllerData> controller_list;
|
||||||
|
std::unordered_map<int, InputIdentifier> callback_list;
|
||||||
|
MappingCallback mapping_callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
212
src/input_common/input_mapping.cpp
Normal file
212
src/input_common/input_mapping.cpp
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
#include "input_common/input_mapping.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
MappingFactory::MappingFactory() = default;
|
||||||
|
|
||||||
|
void MappingFactory::BeginMapping(Polling::InputType type) {
|
||||||
|
is_enabled = true;
|
||||||
|
input_type = type;
|
||||||
|
input_queue.Clear();
|
||||||
|
first_axis = -1;
|
||||||
|
second_axis = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage MappingFactory::GetNextInput() {
|
||||||
|
Common::ParamPackage input;
|
||||||
|
input_queue.Pop(input);
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MappingFactory::RegisterInput(const MappingData& data) {
|
||||||
|
if (!is_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!IsDriverValid(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (input_type) {
|
||||||
|
case Polling::InputType::Button:
|
||||||
|
RegisterButton(data);
|
||||||
|
return;
|
||||||
|
case Polling::InputType::Stick:
|
||||||
|
RegisterStick(data);
|
||||||
|
return;
|
||||||
|
case Polling::InputType::Motion:
|
||||||
|
RegisterMotion(data);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MappingFactory::StopMapping() {
|
||||||
|
is_enabled = false;
|
||||||
|
input_type = Polling::InputType::None;
|
||||||
|
input_queue.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MappingFactory::RegisterButton(const MappingData& data) {
|
||||||
|
Common::ParamPackage new_input;
|
||||||
|
new_input.Set("engine", data.engine);
|
||||||
|
if (data.pad.guid.IsValid()) {
|
||||||
|
new_input.Set("guid", data.pad.guid.RawString());
|
||||||
|
}
|
||||||
|
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||||
|
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case EngineInputType::Button:
|
||||||
|
// Workaround for old compatibility
|
||||||
|
if (data.engine == "keyboard") {
|
||||||
|
new_input.Set("code", data.index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
new_input.Set("button", data.index);
|
||||||
|
break;
|
||||||
|
case EngineInputType::HatButton:
|
||||||
|
new_input.Set("hat", data.index);
|
||||||
|
new_input.Set("direction", data.hat_name);
|
||||||
|
break;
|
||||||
|
case EngineInputType::Analog:
|
||||||
|
// Ignore mouse axis when mapping buttons
|
||||||
|
if (data.engine == "mouse") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_input.Set("axis", data.index);
|
||||||
|
new_input.Set("threshold", 0.5f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input_queue.Push(new_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MappingFactory::RegisterStick(const MappingData& data) {
|
||||||
|
Common::ParamPackage new_input;
|
||||||
|
new_input.Set("engine", data.engine);
|
||||||
|
if (data.pad.guid.IsValid()) {
|
||||||
|
new_input.Set("guid", data.pad.guid.RawString());
|
||||||
|
}
|
||||||
|
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||||
|
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||||
|
|
||||||
|
// If engine is mouse map the mouse position as a joystick
|
||||||
|
if (data.engine == "mouse") {
|
||||||
|
new_input.Set("axis_x", 0);
|
||||||
|
new_input.Set("axis_y", 1);
|
||||||
|
new_input.Set("threshold", 0.5f);
|
||||||
|
new_input.Set("range", 1.0f);
|
||||||
|
new_input.Set("deadzone", 0.0f);
|
||||||
|
input_queue.Push(new_input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case EngineInputType::Button:
|
||||||
|
case EngineInputType::HatButton:
|
||||||
|
RegisterButton(data);
|
||||||
|
return;
|
||||||
|
case EngineInputType::Analog:
|
||||||
|
if (first_axis == data.index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (first_axis == -1) {
|
||||||
|
first_axis = data.index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_input.Set("axis_x", first_axis);
|
||||||
|
new_input.Set("axis_y", data.index);
|
||||||
|
new_input.Set("threshold", 0.5f);
|
||||||
|
new_input.Set("range", 0.95f);
|
||||||
|
new_input.Set("deadzone", 0.15f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input_queue.Push(new_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MappingFactory::RegisterMotion(const MappingData& data) {
|
||||||
|
Common::ParamPackage new_input;
|
||||||
|
new_input.Set("engine", data.engine);
|
||||||
|
if (data.pad.guid.IsValid()) {
|
||||||
|
new_input.Set("guid", data.pad.guid.RawString());
|
||||||
|
}
|
||||||
|
new_input.Set("port", static_cast<int>(data.pad.port));
|
||||||
|
new_input.Set("pad", static_cast<int>(data.pad.pad));
|
||||||
|
|
||||||
|
// If engine is mouse map the mouse position as 3 axis motion
|
||||||
|
if (data.engine == "mouse") {
|
||||||
|
new_input.Set("axis_x", 1);
|
||||||
|
new_input.Set("invert_x", "-");
|
||||||
|
new_input.Set("axis_y", 0);
|
||||||
|
new_input.Set("axis_z", 4);
|
||||||
|
new_input.Set("range", 1.0f);
|
||||||
|
new_input.Set("deadzone", 0.0f);
|
||||||
|
input_queue.Push(new_input);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case EngineInputType::Button:
|
||||||
|
case EngineInputType::HatButton:
|
||||||
|
RegisterButton(data);
|
||||||
|
return;
|
||||||
|
case EngineInputType::Analog:
|
||||||
|
if (first_axis == data.index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (second_axis == data.index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (first_axis == -1) {
|
||||||
|
first_axis = data.index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (second_axis == -1) {
|
||||||
|
second_axis = data.index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new_input.Set("axis_x", first_axis);
|
||||||
|
new_input.Set("axis_y", second_axis);
|
||||||
|
new_input.Set("axis_z", data.index);
|
||||||
|
new_input.Set("range", 1.0f);
|
||||||
|
new_input.Set("deadzone", 0.20f);
|
||||||
|
break;
|
||||||
|
case EngineInputType::Motion:
|
||||||
|
new_input.Set("motion", data.index);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input_queue.Push(new_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MappingFactory::IsDriverValid(const MappingData& data) const {
|
||||||
|
// Only port 0 can be mapped on the keyboard
|
||||||
|
if (data.engine == "keyboard" && data.pad.port != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// To prevent mapping with two devices we disable any UDP except motion
|
||||||
|
if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
|
||||||
|
data.type != EngineInputType::Motion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The following drivers don't need to be mapped
|
||||||
|
if (data.engine == "touch_from_button") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data.engine == "analog_from_button") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
88
src/input_common/input_mapping.h
Normal file
88
src/input_common/input_mapping.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/param_package.h"
|
||||||
|
#include "common/threadsafe_queue.h"
|
||||||
|
|
||||||
|
namespace InputCommon::Polling {
|
||||||
|
enum class InputType;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
class InputEngine;
|
||||||
|
struct MappingData;
|
||||||
|
|
||||||
|
class MappingFactory {
|
||||||
|
public:
|
||||||
|
MappingFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all variables to begin the mapping process
|
||||||
|
* @param type type of input desired to be returned
|
||||||
|
*/
|
||||||
|
void BeginMapping(Polling::InputType type);
|
||||||
|
|
||||||
|
/// Returns an input event with mapping information from the input_queue
|
||||||
|
[[nodiscard]] Common::ParamPackage GetNextInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers mapping input data from the driver
|
||||||
|
* @param data A struct containing all the information needed to create a proper
|
||||||
|
* ParamPackage
|
||||||
|
*/
|
||||||
|
void RegisterInput(const MappingData& data);
|
||||||
|
|
||||||
|
/// Stop polling from all backends
|
||||||
|
void StopMapping();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* If provided data satisfies the requirements it will push an element to the input_queue
|
||||||
|
* Supported input:
|
||||||
|
* - Button: Creates a basic button ParamPackage
|
||||||
|
* - HatButton: Creates a basic hat button ParamPackage
|
||||||
|
* - Analog: Creates a basic analog ParamPackage
|
||||||
|
* @param data A struct containing all the information needed to create a proper
|
||||||
|
* ParamPackage
|
||||||
|
*/
|
||||||
|
void RegisterButton(const MappingData& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If provided data satisfies the requirements it will push an element to the input_queue
|
||||||
|
* Supported input:
|
||||||
|
* - Button, HatButton: Pass the data to RegisterButton
|
||||||
|
* - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage
|
||||||
|
* @param data A struct containing all the information needed to create a proper
|
||||||
|
* ParamPackage
|
||||||
|
*/
|
||||||
|
void RegisterStick(const MappingData& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If provided data satisfies the requirements it will push an element to the input_queue
|
||||||
|
* Supported input:
|
||||||
|
* - Button, HatButton: Pass the data to RegisterButton
|
||||||
|
* - Analog: Stores the first two axis and on the third axis creates a basic Motion
|
||||||
|
* ParamPackage
|
||||||
|
* - Motion: Creates a basic Motion ParamPackage
|
||||||
|
* @param data A struct containing all the information needed to create a proper
|
||||||
|
* ParamPackage
|
||||||
|
*/
|
||||||
|
void RegisterMotion(const MappingData& data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if driver can be mapped
|
||||||
|
* @param data A struct containing all the information needed to create a proper
|
||||||
|
* ParamPackage
|
||||||
|
*/
|
||||||
|
bool IsDriverValid(const MappingData& data) const;
|
||||||
|
|
||||||
|
Common::SPSCQueue<Common::ParamPackage> input_queue;
|
||||||
|
Polling::InputType input_type{Polling::InputType::None};
|
||||||
|
bool is_enabled{};
|
||||||
|
int first_axis = -1;
|
||||||
|
int second_axis = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
790
src/input_common/input_poller.cpp
Normal file
790
src/input_common/input_poller.cpp
Normal file
@ -0,0 +1,790 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/input.h"
|
||||||
|
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
#include "input_common/input_poller.h"
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
|
||||||
|
class DummyInput final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit DummyInput() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromButton final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
|
||||||
|
input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Button,
|
||||||
|
.index = button,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_button_value = false;
|
||||||
|
callback_key = input_engine->SetCallback(input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromButton() override {
|
||||||
|
input_engine->DeleteCallback(callback_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonStatus GetStatus() const {
|
||||||
|
return {
|
||||||
|
.value = input_engine->GetButton(identifier, button),
|
||||||
|
.inverted = inverted,
|
||||||
|
.toggle = toggle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Button,
|
||||||
|
.button_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
last_button_value = status.button_status.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Button,
|
||||||
|
.button_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.button_status.value != last_button_value) {
|
||||||
|
last_button_value = status.button_status.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int button;
|
||||||
|
const bool toggle;
|
||||||
|
const bool inverted;
|
||||||
|
int callback_key;
|
||||||
|
bool last_button_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromHatButton final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
|
||||||
|
bool inverted_, InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
|
||||||
|
inverted(inverted_), input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::HatButton,
|
||||||
|
.index = button,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_button_value = false;
|
||||||
|
callback_key = input_engine->SetCallback(input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromHatButton() override {
|
||||||
|
input_engine->DeleteCallback(callback_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonStatus GetStatus() const {
|
||||||
|
return {
|
||||||
|
.value = input_engine->GetHatButton(identifier, button, direction),
|
||||||
|
.inverted = inverted,
|
||||||
|
.toggle = toggle,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Button,
|
||||||
|
.button_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
last_button_value = status.button_status.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Button,
|
||||||
|
.button_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.button_status.value != last_button_value) {
|
||||||
|
last_button_value = status.button_status.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int button;
|
||||||
|
const u8 direction;
|
||||||
|
const bool toggle;
|
||||||
|
const bool inverted;
|
||||||
|
int callback_key;
|
||||||
|
bool last_button_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromStick final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_,
|
||||||
|
Common::Input::AnalogProperties properties_x_,
|
||||||
|
Common::Input::AnalogProperties properties_y_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||||
|
properties_y(properties_y_),
|
||||||
|
input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier x_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_x,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
const InputIdentifier y_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_y,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_axis_x_value = 0.0f;
|
||||||
|
last_axis_y_value = 0.0f;
|
||||||
|
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||||
|
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromStick() override {
|
||||||
|
input_engine->DeleteCallback(callback_key_x);
|
||||||
|
input_engine->DeleteCallback(callback_key_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::StickStatus GetStatus() const {
|
||||||
|
Common::Input::StickStatus status;
|
||||||
|
status.x = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||||
|
.properties = properties_x,
|
||||||
|
};
|
||||||
|
status.y = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||||
|
.properties = properties_y,
|
||||||
|
};
|
||||||
|
// This is a workaround to keep compatibility with old yuzu versions. Vertical axis is
|
||||||
|
// inverted on SDL compared to Nintendo
|
||||||
|
if (invert_axis_y) {
|
||||||
|
status.y.raw_value = -status.y.raw_value;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Stick,
|
||||||
|
.stick_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
last_axis_x_value = status.stick_status.x.raw_value;
|
||||||
|
last_axis_y_value = status.stick_status.y.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Stick,
|
||||||
|
.stick_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.stick_status.x.raw_value != last_axis_x_value ||
|
||||||
|
status.stick_status.y.raw_value != last_axis_y_value) {
|
||||||
|
last_axis_x_value = status.stick_status.x.raw_value;
|
||||||
|
last_axis_y_value = status.stick_status.y.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int axis_x;
|
||||||
|
const int axis_y;
|
||||||
|
const Common::Input::AnalogProperties properties_x;
|
||||||
|
const Common::Input::AnalogProperties properties_y;
|
||||||
|
int callback_key_x;
|
||||||
|
int callback_key_y;
|
||||||
|
float last_axis_x_value;
|
||||||
|
float last_axis_y_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
const bool invert_axis_y;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromTouch final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromTouch(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
|
||||||
|
int axis_x_, int axis_y_, Common::Input::AnalogProperties properties_x_,
|
||||||
|
Common::Input::AnalogProperties properties_y_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
|
||||||
|
axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||||
|
properties_y(properties_y_), input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier button_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Button,
|
||||||
|
.index = button,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
const InputIdentifier x_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_x,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
const InputIdentifier y_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_y,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_axis_x_value = 0.0f;
|
||||||
|
last_axis_y_value = 0.0f;
|
||||||
|
last_button_value = false;
|
||||||
|
callback_key_button = input_engine->SetCallback(button_input_identifier);
|
||||||
|
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||||
|
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromTouch() override {
|
||||||
|
input_engine->DeleteCallback(callback_key_button);
|
||||||
|
input_engine->DeleteCallback(callback_key_x);
|
||||||
|
input_engine->DeleteCallback(callback_key_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::TouchStatus GetStatus() const {
|
||||||
|
Common::Input::TouchStatus status{};
|
||||||
|
status.pressed = {
|
||||||
|
.value = input_engine->GetButton(identifier, button),
|
||||||
|
.inverted = inverted,
|
||||||
|
.toggle = toggle,
|
||||||
|
};
|
||||||
|
status.x = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||||
|
.properties = properties_x,
|
||||||
|
};
|
||||||
|
status.y = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||||
|
.properties = properties_y,
|
||||||
|
};
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Touch,
|
||||||
|
.touch_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.touch_status.x.raw_value != last_axis_x_value ||
|
||||||
|
status.touch_status.y.raw_value != last_axis_y_value ||
|
||||||
|
status.touch_status.pressed.value != last_button_value) {
|
||||||
|
last_axis_x_value = status.touch_status.x.raw_value;
|
||||||
|
last_axis_y_value = status.touch_status.y.raw_value;
|
||||||
|
last_button_value = status.touch_status.pressed.value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int button;
|
||||||
|
const bool toggle;
|
||||||
|
const bool inverted;
|
||||||
|
const int axis_x;
|
||||||
|
const int axis_y;
|
||||||
|
const Common::Input::AnalogProperties properties_x;
|
||||||
|
const Common::Input::AnalogProperties properties_y;
|
||||||
|
int callback_key_button;
|
||||||
|
int callback_key_x;
|
||||||
|
int callback_key_y;
|
||||||
|
bool last_button_value;
|
||||||
|
float last_axis_x_value;
|
||||||
|
float last_axis_y_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromAnalog final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromAnalog(PadIdentifier identifier_, int axis_,
|
||||||
|
Common::Input::AnalogProperties properties_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), axis(axis_), properties(properties_),
|
||||||
|
input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_axis_value = 0.0f;
|
||||||
|
callback_key = input_engine->SetCallback(input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromAnalog() override {
|
||||||
|
input_engine->DeleteCallback(callback_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::AnalogStatus GetStatus() const {
|
||||||
|
return {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis),
|
||||||
|
.properties = properties,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Analog,
|
||||||
|
.analog_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.analog_status.raw_value != last_axis_value) {
|
||||||
|
last_axis_value = status.analog_status.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int axis;
|
||||||
|
const Common::Input::AnalogProperties properties;
|
||||||
|
int callback_key;
|
||||||
|
float last_axis_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromMotion final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), motion_sensor(motion_sensor_), gyro_threshold(gyro_threshold_),
|
||||||
|
input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Motion,
|
||||||
|
.index = motion_sensor,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
callback_key = input_engine->SetCallback(input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromMotion() override {
|
||||||
|
input_engine->DeleteCallback(callback_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::MotionStatus GetStatus() const {
|
||||||
|
const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
|
||||||
|
Common::Input::MotionStatus status{};
|
||||||
|
const Common::Input::AnalogProperties properties = {
|
||||||
|
.deadzone = 0.0f,
|
||||||
|
.range = 1.0f,
|
||||||
|
.threshold = gyro_threshold,
|
||||||
|
.offset = 0.0f,
|
||||||
|
};
|
||||||
|
status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties};
|
||||||
|
status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties};
|
||||||
|
status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties};
|
||||||
|
status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties};
|
||||||
|
status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties};
|
||||||
|
status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties};
|
||||||
|
status.delta_timestamp = basic_motion.delta_timestamp;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Motion,
|
||||||
|
.motion_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int motion_sensor;
|
||||||
|
const float gyro_threshold;
|
||||||
|
int callback_key;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputFromAxisMotion final : public Common::Input::InputDevice {
|
||||||
|
public:
|
||||||
|
explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_,
|
||||||
|
Common::Input::AnalogProperties properties_x_,
|
||||||
|
Common::Input::AnalogProperties properties_y_,
|
||||||
|
Common::Input::AnalogProperties properties_z_,
|
||||||
|
InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_),
|
||||||
|
properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_),
|
||||||
|
input_engine(input_engine_) {
|
||||||
|
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||||
|
const InputIdentifier x_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_x,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
const InputIdentifier y_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_y,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
const InputIdentifier z_input_identifier{
|
||||||
|
.identifier = identifier,
|
||||||
|
.type = EngineInputType::Analog,
|
||||||
|
.index = axis_z,
|
||||||
|
.callback = engine_callback,
|
||||||
|
};
|
||||||
|
last_axis_x_value = 0.0f;
|
||||||
|
last_axis_y_value = 0.0f;
|
||||||
|
last_axis_z_value = 0.0f;
|
||||||
|
callback_key_x = input_engine->SetCallback(x_input_identifier);
|
||||||
|
callback_key_y = input_engine->SetCallback(y_input_identifier);
|
||||||
|
callback_key_z = input_engine->SetCallback(z_input_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
~InputFromAxisMotion() override {
|
||||||
|
input_engine->DeleteCallback(callback_key_x);
|
||||||
|
input_engine->DeleteCallback(callback_key_y);
|
||||||
|
input_engine->DeleteCallback(callback_key_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::MotionStatus GetStatus() const {
|
||||||
|
Common::Input::MotionStatus status{};
|
||||||
|
status.gyro.x = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_x),
|
||||||
|
.properties = properties_x,
|
||||||
|
};
|
||||||
|
status.gyro.y = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_y),
|
||||||
|
.properties = properties_y,
|
||||||
|
};
|
||||||
|
status.gyro.z = {
|
||||||
|
.raw_value = input_engine->GetAxis(identifier, axis_z),
|
||||||
|
.properties = properties_z,
|
||||||
|
};
|
||||||
|
status.delta_timestamp = 5000;
|
||||||
|
status.force_update = true;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ForceUpdate() override {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Motion,
|
||||||
|
.motion_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
last_axis_x_value = status.motion_status.gyro.x.raw_value;
|
||||||
|
last_axis_y_value = status.motion_status.gyro.y.raw_value;
|
||||||
|
last_axis_z_value = status.motion_status.gyro.z.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnChange() {
|
||||||
|
const Common::Input::CallbackStatus status{
|
||||||
|
.type = Common::Input::InputType::Motion,
|
||||||
|
.motion_status = GetStatus(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (status.motion_status.gyro.x.raw_value != last_axis_x_value ||
|
||||||
|
status.motion_status.gyro.y.raw_value != last_axis_y_value ||
|
||||||
|
status.motion_status.gyro.z.raw_value != last_axis_z_value) {
|
||||||
|
last_axis_x_value = status.motion_status.gyro.x.raw_value;
|
||||||
|
last_axis_y_value = status.motion_status.gyro.y.raw_value;
|
||||||
|
last_axis_z_value = status.motion_status.gyro.z.raw_value;
|
||||||
|
TriggerOnChange(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
const int axis_x;
|
||||||
|
const int axis_y;
|
||||||
|
const int axis_z;
|
||||||
|
const Common::Input::AnalogProperties properties_x;
|
||||||
|
const Common::Input::AnalogProperties properties_y;
|
||||||
|
const Common::Input::AnalogProperties properties_z;
|
||||||
|
int callback_key_x;
|
||||||
|
int callback_key_y;
|
||||||
|
int callback_key_z;
|
||||||
|
float last_axis_x_value;
|
||||||
|
float last_axis_y_value;
|
||||||
|
float last_axis_z_value;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OutputFromIdentifier final : public Common::Input::OutputDevice {
|
||||||
|
public:
|
||||||
|
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
|
||||||
|
: identifier(identifier_), input_engine(input_engine_) {}
|
||||||
|
|
||||||
|
Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
|
||||||
|
return input_engine->SetPollingMode(identifier, polling_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const PadIdentifier identifier;
|
||||||
|
InputEngine* input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto button_id = params.Get("button", 0);
|
||||||
|
const auto keyboard_key = params.Get("code", 0);
|
||||||
|
const auto toggle = params.Get("toggle", false) != 0;
|
||||||
|
const auto inverted = params.Get("inverted", false) != 0;
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetButton(identifier, button_id);
|
||||||
|
input_engine->PreSetButton(identifier, keyboard_key);
|
||||||
|
if (keyboard_key != 0) {
|
||||||
|
return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
|
||||||
|
input_engine.get());
|
||||||
|
}
|
||||||
|
return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
|
||||||
|
input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto button_id = params.Get("hat", 0);
|
||||||
|
const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
|
||||||
|
const auto toggle = params.Get("toggle", false) != 0;
|
||||||
|
const auto inverted = params.Get("inverted", false) != 0;
|
||||||
|
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetHatButton(identifier, button_id);
|
||||||
|
return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
|
||||||
|
input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
|
||||||
|
const auto range = std::clamp(params.Get("range", 0.95f), 0.25f, 1.50f);
|
||||||
|
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis_x = params.Get("axis_x", 0);
|
||||||
|
const Common::Input::AnalogProperties properties_x = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_x", "+") == "-",
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis_y = params.Get("axis_y", 1);
|
||||||
|
const Common::Input::AnalogProperties properties_y = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_y", "+") != "+",
|
||||||
|
};
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_x);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_y);
|
||||||
|
return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
|
||||||
|
input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis = params.Get("axis", 0);
|
||||||
|
const Common::Input::AnalogProperties properties = {
|
||||||
|
.deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
|
||||||
|
.range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f),
|
||||||
|
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
|
||||||
|
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert", "+") == "-",
|
||||||
|
.toggle = params.Get("toggle", false) != 0,
|
||||||
|
};
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetAxis(identifier, axis);
|
||||||
|
return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
|
||||||
|
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||||
|
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto button = params.Get("button", 0);
|
||||||
|
const auto toggle = params.Get("toggle", false) != 0;
|
||||||
|
const auto inverted = params.Get("inverted", false) != 0;
|
||||||
|
|
||||||
|
const auto axis_x = params.Get("axis_x", 0);
|
||||||
|
const Common::Input::AnalogProperties properties_x = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_x", "+") == "-",
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis_y = params.Get("axis_y", 1);
|
||||||
|
const Common::Input::AnalogProperties properties_y = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_y", false) != 0,
|
||||||
|
};
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_x);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_y);
|
||||||
|
input_engine->PreSetButton(identifier, button);
|
||||||
|
return std::make_unique<InputFromTouch>(identifier, button, toggle, inverted, axis_x, axis_y,
|
||||||
|
properties_x, properties_y, input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
|
||||||
|
Common::ParamPackage params) {
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.Has("motion")) {
|
||||||
|
const auto motion_sensor = params.Get("motion", 0);
|
||||||
|
const auto gyro_threshold = params.Get("threshold", 0.007f);
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetMotion(identifier, motion_sensor);
|
||||||
|
return std::make_unique<InputFromMotion>(identifier, motion_sensor, gyro_threshold,
|
||||||
|
input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
|
||||||
|
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||||
|
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||||
|
|
||||||
|
const auto axis_x = params.Get("axis_x", 0);
|
||||||
|
const Common::Input::AnalogProperties properties_x = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_x", "+") == "-",
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis_y = params.Get("axis_y", 1);
|
||||||
|
const Common::Input::AnalogProperties properties_y = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_y", "+") != "+",
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto axis_z = params.Get("axis_z", 1);
|
||||||
|
const Common::Input::AnalogProperties properties_z = {
|
||||||
|
.deadzone = deadzone,
|
||||||
|
.range = range,
|
||||||
|
.threshold = threshold,
|
||||||
|
.offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
|
||||||
|
.inverted = params.Get("invert_z", "+") != "+",
|
||||||
|
};
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_x);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_y);
|
||||||
|
input_engine->PreSetAxis(identifier, axis_z);
|
||||||
|
return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
|
||||||
|
properties_y, properties_z, input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
|
||||||
|
: input_engine(std::move(input_engine_)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) {
|
||||||
|
return CreateTouchDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("button") || params.Has("code")) {
|
||||||
|
return CreateButtonDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("hat")) {
|
||||||
|
return CreateHatButtonDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
|
||||||
|
return CreateMotionDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("motion")) {
|
||||||
|
return CreateMotionDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis_x") && params.Has("axis_y")) {
|
||||||
|
return CreateStickDevice(params);
|
||||||
|
}
|
||||||
|
if (params.Has("axis")) {
|
||||||
|
return CreateAnalogDevice(params);
|
||||||
|
}
|
||||||
|
LOG_ERROR(Input, "Invalid parameters given");
|
||||||
|
return std::make_unique<DummyInput>();
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
|
||||||
|
: input_engine(std::move(input_engine_)) {}
|
||||||
|
|
||||||
|
std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
|
||||||
|
const Common::ParamPackage& params) {
|
||||||
|
const PadIdentifier identifier = {
|
||||||
|
.guid = Common::UUID{params.Get("guid", "")},
|
||||||
|
.port = static_cast<std::size_t>(params.Get("port", 0)),
|
||||||
|
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
input_engine->PreSetController(identifier);
|
||||||
|
return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace InputCommon
|
181
src/input_common/input_poller.h
Normal file
181
src/input_common/input_poller.h
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Input {
|
||||||
|
class InputDevice;
|
||||||
|
|
||||||
|
template <typename InputDevice>
|
||||||
|
class Factory;
|
||||||
|
}; // namespace Input
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
class InputEngine;
|
||||||
|
|
||||||
|
class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> {
|
||||||
|
public:
|
||||||
|
explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an output device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "guid" text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique output device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::OutputDevice> Create(
|
||||||
|
const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<InputEngine> input_engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Input factory. It receives input events and forward them to all input devices it created.
|
||||||
|
*/
|
||||||
|
class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
|
||||||
|
public:
|
||||||
|
explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an input device from the parameters given. Identifies the type of input to be
|
||||||
|
* returned if it contains the following parameters:
|
||||||
|
* - button: Contains "button" or "code"
|
||||||
|
* - hat_button: Contains "hat"
|
||||||
|
* - analog: Contains "axis"
|
||||||
|
* - stick: Contains "axis_x" and "axis_y"
|
||||||
|
* - motion: Contains "axis_x", "axis_y" and "axis_z"
|
||||||
|
* - motion: Contains "motion"
|
||||||
|
* - touch: Contains "button", "axis_x" and "axis_y"
|
||||||
|
* - output: Contains "output"
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "code": the code of the keyboard key to bind with the input
|
||||||
|
* - "button": same as "code" but for controller buttons
|
||||||
|
* - "hat": similar as "button" but it's a group of hat buttons from SDL
|
||||||
|
* - "axis": the axis number of the axis to bind with the input
|
||||||
|
* - "motion": the motion number of the motion to bind with the input
|
||||||
|
* - "axis_x": same as axis but specifying horizontal direction
|
||||||
|
* - "axis_y": same as axis but specifying vertical direction
|
||||||
|
* - "axis_z": same as axis but specifying forward direction
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Creates a button device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "code": the code of the keyboard key to bind with the input
|
||||||
|
* - "button": same as "code" but for controller buttons
|
||||||
|
* - "toggle": press once to enable, press again to disable
|
||||||
|
* - "inverted": inverts the output of the button
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice(
|
||||||
|
const Common::ParamPackage& params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a hat button device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "button": the controller hat id to bind with the input
|
||||||
|
* - "direction": the direction id to be detected
|
||||||
|
* - "toggle": press once to enable, press again to disable
|
||||||
|
* - "inverted": inverts the output of the button
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice(
|
||||||
|
const Common::ParamPackage& params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stick device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "axis_x": the controller horizontal axis id to bind with the input
|
||||||
|
* - "axis_y": the controller vertical axis id to bind with the input
|
||||||
|
* - "deadzone": the minimum required value to be detected
|
||||||
|
* - "range": the maximum value required to reach 100%
|
||||||
|
* - "threshold": the minimum required value to considered pressed
|
||||||
|
* - "offset_x": the amount of offset in the x axis
|
||||||
|
* - "offset_y": the amount of offset in the y axis
|
||||||
|
* - "invert_x": inverts the sign of the horizontal axis
|
||||||
|
* - "invert_y": inverts the sign of the vertical axis
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateStickDevice(
|
||||||
|
const Common::ParamPackage& params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analog device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "axis": the controller axis id to bind with the input
|
||||||
|
* - "deadzone": the minimum required value to be detected
|
||||||
|
* - "range": the maximum value required to reach 100%
|
||||||
|
* - "threshold": the minimum required value to considered pressed
|
||||||
|
* - "offset": the amount of offset in the axis
|
||||||
|
* - "invert": inverts the sign of the axis
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice(
|
||||||
|
const Common::ParamPackage& params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a touch device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "button": the controller hat id to bind with the input
|
||||||
|
* - "direction": the direction id to be detected
|
||||||
|
* - "toggle": press once to enable, press again to disable
|
||||||
|
* - "inverted": inverts the output of the button
|
||||||
|
* - "axis_x": the controller horizontal axis id to bind with the input
|
||||||
|
* - "axis_y": the controller vertical axis id to bind with the input
|
||||||
|
* - "deadzone": the minimum required value to be detected
|
||||||
|
* - "range": the maximum value required to reach 100%
|
||||||
|
* - "threshold": the minimum required value to considered pressed
|
||||||
|
* - "offset_x": the amount of offset in the x axis
|
||||||
|
* - "offset_y": the amount of offset in the y axis
|
||||||
|
* - "invert_x": inverts the sign of the horizontal axis
|
||||||
|
* - "invert_y": inverts the sign of the vertical axis
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice(
|
||||||
|
const Common::ParamPackage& params);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a motion device from the parameters given.
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "axis_x": the controller horizontal axis id to bind with the input
|
||||||
|
* - "axis_y": the controller vertical axis id to bind with the input
|
||||||
|
* - "axis_z": the controller forward axis id to bind with the input
|
||||||
|
* - "deadzone": the minimum required value to be detected
|
||||||
|
* - "range": the maximum value required to reach 100%
|
||||||
|
* - "offset_x": the amount of offset in the x axis
|
||||||
|
* - "offset_y": the amount of offset in the y axis
|
||||||
|
* - "offset_z": the amount of offset in the z axis
|
||||||
|
* - "invert_x": inverts the sign of the horizontal axis
|
||||||
|
* - "invert_y": inverts the sign of the vertical axis
|
||||||
|
* - "invert_z": inverts the sign of the forward axis
|
||||||
|
* - "guid": text string for identifying controllers
|
||||||
|
* - "port": port of the connected device
|
||||||
|
* - "pad": slot of the connected controller
|
||||||
|
* @returns a unique input device with the parameters specified
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
|
||||||
|
|
||||||
|
std::shared_ptr<InputEngine> input_engine;
|
||||||
|
};
|
||||||
|
} // namespace InputCommon
|
@ -1,89 +0,0 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
|
||||||
#include <list>
|
|
||||||
#include <mutex>
|
|
||||||
#include <utility>
|
|
||||||
#include "input_common/keyboard.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
class KeyButton final : public Input::ButtonDevice {
|
|
||||||
public:
|
|
||||||
explicit KeyButton(std::atomic<bool>& status_) : status(status_) {}
|
|
||||||
|
|
||||||
~KeyButton() override = default;
|
|
||||||
|
|
||||||
bool GetStatus() const override {
|
|
||||||
return status.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
friend class KeyButtonList;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic<bool>& status;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct KeyButtonPair {
|
|
||||||
explicit KeyButtonPair(int key_code_) : key_code(key_code_) {}
|
|
||||||
int key_code;
|
|
||||||
std::atomic<bool> status{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
class KeyButtonList {
|
|
||||||
public:
|
|
||||||
KeyButtonPair& AddKeyButton(int key_code) {
|
|
||||||
std::lock_guard guard{mutex};
|
|
||||||
auto it = std::find_if(list.begin(), list.end(), [key_code](const KeyButtonPair& pair) {
|
|
||||||
return pair.key_code == key_code;
|
|
||||||
});
|
|
||||||
if (it == list.end()) {
|
|
||||||
return list.emplace_back(key_code);
|
|
||||||
}
|
|
||||||
return *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChangeKeyStatus(int key_code, bool pressed) {
|
|
||||||
std::lock_guard guard{mutex};
|
|
||||||
for (KeyButtonPair& pair : list) {
|
|
||||||
if (pair.key_code == key_code)
|
|
||||||
pair.status.store(pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChangeAllKeyStatus(bool pressed) {
|
|
||||||
std::lock_guard guard{mutex};
|
|
||||||
for (KeyButtonPair& pair : list) {
|
|
||||||
pair.status.store(pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::mutex mutex;
|
|
||||||
std::list<KeyButtonPair> list;
|
|
||||||
};
|
|
||||||
|
|
||||||
Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
|
|
||||||
|
|
||||||
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
|
|
||||||
int key_code = params.Get("code", 0);
|
|
||||||
auto& pair = key_button_list->AddKeyButton(key_code);
|
|
||||||
return std::make_unique<KeyButton>(pair.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Keyboard::PressKey(int key_code) {
|
|
||||||
key_button_list->ChangeKeyStatus(key_code, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Keyboard::ReleaseKey(int key_code) {
|
|
||||||
key_button_list->ChangeKeyStatus(key_code, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Keyboard::ReleaseAllKeys() {
|
|
||||||
key_button_list->ChangeAllKeyStatus(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,47 +0,0 @@
|
|||||||
// Copyright 2017 Citra 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"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
class KeyButtonList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A button device factory representing a keyboard. It receives keyboard events and forward them
|
|
||||||
* to all button devices it created.
|
|
||||||
*/
|
|
||||||
class Keyboard final : public Input::Factory<Input::ButtonDevice> {
|
|
||||||
public:
|
|
||||||
Keyboard();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a button device from a keyboard key
|
|
||||||
* @param params contains parameters for creating the device:
|
|
||||||
* - "code": the code of the key to bind with the button
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the status of all buttons bound with the key to pressed
|
|
||||||
* @param key_code the code of the key to press
|
|
||||||
*/
|
|
||||||
void PressKey(int key_code);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the status of all buttons bound with the key to released
|
|
||||||
* @param key_code the code of the key to release
|
|
||||||
*/
|
|
||||||
void ReleaseKey(int key_code);
|
|
||||||
|
|
||||||
void ReleaseAllKeys();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<KeyButtonList> key_button_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,27 +1,41 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include "common/input.h"
|
||||||
#include "common/param_package.h"
|
#include "common/param_package.h"
|
||||||
|
<<<<<<< HEAD
|
||||||
#include "input_common/analog_from_button.h"
|
#include "input_common/analog_from_button.h"
|
||||||
#ifdef ENABLE_GCADAPTER
|
#ifdef ENABLE_GCADAPTER
|
||||||
#include "input_common/gcadapter/gc_adapter.h"
|
#include "input_common/gcadapter/gc_adapter.h"
|
||||||
#include "input_common/gcadapter/gc_poller.h"
|
#include "input_common/gcadapter/gc_poller.h"
|
||||||
#endif
|
#endif
|
||||||
#include "input_common/keyboard.h"
|
#include "input_common/keyboard.h"
|
||||||
|
== == == =
|
||||||
|
#include "input_common/drivers/keyboard.h"
|
||||||
|
#include "input_common/drivers/mouse.h"
|
||||||
|
#include "input_common/drivers/touch_screen.h"
|
||||||
|
#include "input_common/drivers/udp_client.h"
|
||||||
|
#include "input_common/drivers/virtual_gamepad.h"
|
||||||
|
#include "input_common/helpers/stick_from_buttons.h"
|
||||||
|
#include "input_common/helpers/touch_from_buttons.h"
|
||||||
|
#include "input_common/input_engine.h"
|
||||||
|
#include "input_common/input_mapping.h"
|
||||||
|
#include "input_common/input_poller.h"
|
||||||
|
>>>>>>> 6e5fec9fe (add input common changes)
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
#include "input_common/motion_emu.h"
|
|
||||||
#include "input_common/sdl/sdl.h"
|
#ifdef HAVE_LIBUSB
|
||||||
#include "input_common/sdl/sdl_impl.h"
|
#include "input_common/drivers/gc_adapter.h"
|
||||||
#include "input_common/touch_from_button.h"
|
#endif
|
||||||
#include "input_common/udp/udp.h"
|
#ifdef HAVE_SDL2
|
||||||
|
#include "input_common/drivers/sdl_driver.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace InputCommon {
|
||||||
|
|
||||||
#ifdef ENABLE_GCADAPTER
|
<<<<<<< HEAD
|
||||||
std::shared_ptr<GCButtonFactory> gcbuttons;
|
#ifdef ENABLE_GCADAPTER std::shared_ptr < GCButtonFactory> gcbuttons;
|
||||||
std::shared_ptr<GCAnalogFactory> gcanalog;
|
std::shared_ptr<GCAnalogFactory> gcanalog;
|
||||||
std::shared_ptr<GCAdapter::Adapter> gcadapter;
|
std::shared_ptr<GCAdapter::Adapter> gcadapter;
|
||||||
#endif
|
#endif
|
||||||
@ -46,12 +60,262 @@ void Init() {
|
|||||||
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
|
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
|
||||||
Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
|
Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
|
||||||
std::make_shared<TouchFromButtonFactory>());
|
std::make_shared<TouchFromButtonFactory>());
|
||||||
|
== == == = struct InputSubsystem::Impl {
|
||||||
|
template <typename Engine>
|
||||||
|
void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) {
|
||||||
|
MappingCallback mapping_callback{
|
||||||
|
[this](const MappingData& data) { RegisterInput(data); }};
|
||||||
|
|
||||||
sdl = SDL::Init();
|
engine = std::make_shared<Engine>(name);
|
||||||
|
engine->SetMappingCallback(mapping_callback);
|
||||||
|
>>>>>>> 6e5fec9fe (add input common changes)
|
||||||
|
|
||||||
udp = CemuhookUDP::Init();
|
std::shared_ptr<InputFactory> input_factory =
|
||||||
|
std::make_shared<InputFactory>(engine);
|
||||||
|
std::shared_ptr<OutputFactory> output_factory =
|
||||||
|
std::make_shared<OutputFactory>(engine);
|
||||||
|
Common::Input::RegisterInputFactory(engine->GetEngineName(),
|
||||||
|
std::move(input_factory));
|
||||||
|
Common::Input::RegisterOutputFactory(engine->GetEngineName(),
|
||||||
|
std::move(output_factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Initialize() {
|
||||||
|
mapping_factory = std::make_shared<MappingFactory>();
|
||||||
|
|
||||||
|
RegisterEngine("keyboard", keyboard);
|
||||||
|
RegisterEngine("mouse", mouse);
|
||||||
|
RegisterEngine("touch", touch_screen);
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
RegisterEngine("gcpad", gcadapter);
|
||||||
|
#endif
|
||||||
|
RegisterEngine("cemuhookudp", udp_client);
|
||||||
|
RegisterEngine("virtual_gamepad", virtual_gamepad);
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
RegisterEngine("sdl", sdl);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Common::Input::RegisterInputFactory("touch_from_button",
|
||||||
|
std::make_shared<TouchFromButton>());
|
||||||
|
Common::Input::RegisterInputFactory("analog_from_button",
|
||||||
|
std::make_shared<StickFromButton>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Engine>
|
||||||
|
void UnregisterEngine(std::shared_ptr<Engine>& engine) {
|
||||||
|
Common::Input::UnregisterInputFactory(engine->GetEngineName());
|
||||||
|
Common::Input::UnregisterOutputFactory(engine->GetEngineName());
|
||||||
|
engine.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
UnregisterEngine(keyboard);
|
||||||
|
UnregisterEngine(mouse);
|
||||||
|
UnregisterEngine(touch_screen);
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
UnregisterEngine(gcadapter);
|
||||||
|
#endif
|
||||||
|
UnregisterEngine(udp_client);
|
||||||
|
UnregisterEngine(virtual_gamepad);
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
UnregisterEngine(sdl);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Common::Input::UnregisterInputFactory("touch_from_button");
|
||||||
|
Common::Input::UnregisterInputFactory("analog_from_button");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices = {
|
||||||
|
Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto keyboard_devices = keyboard->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
||||||
|
auto mouse_devices = mouse->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
auto gcadapter_devices = gcadapter->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
||||||
|
#endif
|
||||||
|
auto udp_devices = udp_client->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
auto sdl_devices = sdl->GetInputDevices();
|
||||||
|
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::shared_ptr<InputEngine> GetInputEngine(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const std::string engine = params.Get("engine", "");
|
||||||
|
if (engine == keyboard->GetEngineName()) {
|
||||||
|
return keyboard;
|
||||||
|
}
|
||||||
|
if (engine == mouse->GetEngineName()) {
|
||||||
|
return mouse;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
if (engine == gcadapter->GetEngineName()) {
|
||||||
|
return gcadapter;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (engine == udp_client->GetEngineName()) {
|
||||||
|
return udp_client;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
if (engine == sdl->GetEngineName()) {
|
||||||
|
return sdl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
const auto input_engine = GetInputEngine(params);
|
||||||
|
|
||||||
|
if (input_engine == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_engine->GetAnalogMappingForDevice(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
const auto input_engine = GetInputEngine(params);
|
||||||
|
|
||||||
|
if (input_engine == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_engine->GetButtonMappingForDevice(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
const auto input_engine = GetInputEngine(params);
|
||||||
|
|
||||||
|
if (input_engine == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_engine->GetMotionMappingForDevice(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
|
||||||
|
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
||||||
|
return Common::Input::ButtonNames::Undefined;
|
||||||
|
}
|
||||||
|
const auto input_engine = GetInputEngine(params);
|
||||||
|
|
||||||
|
if (input_engine == nullptr) {
|
||||||
|
return Common::Input::ButtonNames::Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_engine->GetUIName(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsStickInverted(const Common::ParamPackage& params) {
|
||||||
|
const auto input_engine = GetInputEngine(params);
|
||||||
|
|
||||||
|
if (input_engine == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input_engine->IsStickInverted(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsController(const Common::ParamPackage& params) {
|
||||||
|
const std::string engine = params.Get("engine", "");
|
||||||
|
if (engine == mouse->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
if (engine == gcadapter->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (engine == udp_client->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (engine == virtual_gamepad->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
if (engine == sdl->GetEngineName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BeginConfiguration() {
|
||||||
|
keyboard->BeginConfiguration();
|
||||||
|
mouse->BeginConfiguration();
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
gcadapter->BeginConfiguration();
|
||||||
|
#endif
|
||||||
|
udp_client->BeginConfiguration();
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
sdl->BeginConfiguration();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndConfiguration() {
|
||||||
|
keyboard->EndConfiguration();
|
||||||
|
mouse->EndConfiguration();
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
gcadapter->EndConfiguration();
|
||||||
|
#endif
|
||||||
|
udp_client->EndConfiguration();
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
sdl->EndConfiguration();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PumpEvents() const {
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
sdl->PumpEvents();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterInput(const MappingData& data) {
|
||||||
|
mapping_factory->RegisterInput(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MappingFactory> mapping_factory;
|
||||||
|
|
||||||
|
std::shared_ptr<Keyboard> keyboard;
|
||||||
|
std::shared_ptr<Mouse> mouse;
|
||||||
|
std::shared_ptr<TouchScreen> touch_screen;
|
||||||
|
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
||||||
|
std::shared_ptr<VirtualGamepad> virtual_gamepad;
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBUSB
|
||||||
|
std::shared_ptr<GCAdapter> gcadapter;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SDL2
|
||||||
|
std::shared_ptr<SDLDriver> sdl;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
|
||||||
|
|
||||||
|
InputSubsystem::~InputSubsystem() = default;
|
||||||
|
|
||||||
|
void InputSubsystem::Initialize() {
|
||||||
|
impl->Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
#ifdef ENABLE_GCADAPTER
|
#ifdef ENABLE_GCADAPTER
|
||||||
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
|
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
|
||||||
@ -68,26 +332,111 @@ void Shutdown() {
|
|||||||
Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
|
Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
|
||||||
sdl.reset();
|
sdl.reset();
|
||||||
udp.reset();
|
udp.reset();
|
||||||
|
== == == = void InputSubsystem::Shutdown() {
|
||||||
|
impl->Shutdown();
|
||||||
|
>>>>>>> 6e5fec9fe (add input common changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
Keyboard* GetKeyboard() {
|
Keyboard* InputSubsystem::GetKeyboard() {
|
||||||
return keyboard.get();
|
return impl->keyboard.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEmu* GetMotionEmu() {
|
const Keyboard* InputSubsystem::GetKeyboard() const {
|
||||||
return motion_emu.get();
|
return impl->keyboard.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Mouse* InputSubsystem::GetMouse() {
|
||||||
|
return impl->mouse.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Mouse* InputSubsystem::GetMouse() const {
|
||||||
|
return impl->mouse.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchScreen* InputSubsystem::GetTouchScreen() {
|
||||||
|
return impl->touch_screen.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TouchScreen* InputSubsystem::GetTouchScreen() const {
|
||||||
|
return impl->touch_screen.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
VirtualGamepad* InputSubsystem::GetVirtualGamepad() {
|
||||||
|
return impl->virtual_gamepad.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const VirtualGamepad* InputSubsystem::GetVirtualGamepad() const {
|
||||||
|
return impl->virtual_gamepad.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
|
||||||
|
return impl->GetInputDevices();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalogMapping InputSubsystem::GetAnalogMappingForDevice(
|
||||||
|
const Common::ParamPackage& device) const {
|
||||||
|
return impl->GetAnalogMappingForDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonMapping InputSubsystem::GetButtonMappingForDevice(
|
||||||
|
const Common::ParamPackage& device) const {
|
||||||
|
return impl->GetButtonMappingForDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionMapping InputSubsystem::GetMotionMappingForDevice(
|
||||||
|
const Common::ParamPackage& device) const {
|
||||||
|
return impl->GetMotionMappingForDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::Input::ButtonNames InputSubsystem::GetButtonName(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
return impl->GetButtonName(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
|
||||||
|
return impl->IsController(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
|
||||||
|
if (params.Has("axis_x") && params.Has("axis_y")) {
|
||||||
|
return impl->IsStickInverted(params);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSubsystem::ReloadInputDevices() {
|
||||||
|
impl->udp_client.get()->ReloadSockets();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
||||||
|
impl->BeginConfiguration();
|
||||||
|
impl->mapping_factory->BeginMapping(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ParamPackage InputSubsystem::GetNextInput() const {
|
||||||
|
return impl->mapping_factory->GetNextInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSubsystem::StopMapping() const {
|
||||||
|
impl->EndConfiguration();
|
||||||
|
impl->mapping_factory->StopMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSubsystem::PumpEvents() const {
|
||||||
|
impl->PumpEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GenerateKeyboardParam(int key_code) {
|
std::string GenerateKeyboardParam(int key_code) {
|
||||||
Common::ParamPackage param{
|
Common::ParamPackage param;
|
||||||
{"engine", "keyboard"},
|
param.Set("engine", "keyboard");
|
||||||
{"code", std::to_string(key_code)},
|
param.Set("code", key_code);
|
||||||
};
|
param.Set("toggle", false);
|
||||||
return param.Serialize();
|
return param.Serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
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_modifier, float modifier_scale) {
|
int key_right, int key_modifier,
|
||||||
|
float modifier_scale) {
|
||||||
Common::ParamPackage circle_pad_param{
|
Common::ParamPackage circle_pad_param{
|
||||||
{"engine", "analog_from_button"},
|
{"engine", "analog_from_button"},
|
||||||
{"up", GenerateKeyboardParam(key_up)},
|
{"up", GenerateKeyboardParam(key_up)},
|
||||||
@ -99,13 +448,16 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
|
|||||||
};
|
};
|
||||||
return circle_pad_param.Serialize();
|
return circle_pad_param.Serialize();
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button) {
|
Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params,
|
||||||
|
int button) {
|
||||||
const auto native_button{static_cast<Settings::NativeButton::Values>(button)};
|
const auto native_button{static_cast<Settings::NativeButton::Values>(button)};
|
||||||
const auto engine{params.Get("engine", "")};
|
const auto engine{params.Get("engine", "")};
|
||||||
if (engine == "sdl") {
|
if (engine == "sdl") {
|
||||||
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID(
|
return dynamic_cast<SDL::SDLState*>(sdl.get())
|
||||||
params.Get("guid", "0"), params.Get("port", 0), native_button);
|
->GetSDLControllerButtonBindByGUID(params.Get("guid", "0"),
|
||||||
|
params.Get("port", 0), native_button);
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_GCADAPTER
|
#ifdef ENABLE_GCADAPTER
|
||||||
if (engine == "gcpad") {
|
if (engine == "gcpad") {
|
||||||
@ -115,12 +467,14 @@ Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog) {
|
Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params,
|
||||||
|
int analog) {
|
||||||
const auto native_analog{static_cast<Settings::NativeAnalog::Values>(analog)};
|
const auto native_analog{static_cast<Settings::NativeAnalog::Values>(analog)};
|
||||||
const auto engine{params.Get("engine", "")};
|
const auto engine{params.Get("engine", "")};
|
||||||
if (engine == "sdl") {
|
if (engine == "sdl") {
|
||||||
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID(
|
return dynamic_cast<SDL::SDLState*>(sdl.get())
|
||||||
params.Get("guid", "0"), params.Get("port", 0), native_analog);
|
->GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"),
|
||||||
|
params.Get("port", 0), native_analog);
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_GCADAPTER
|
#ifdef ENABLE_GCADAPTER
|
||||||
if (engine == "gcpad") {
|
if (engine == "gcpad") {
|
||||||
@ -162,4 +516,6 @@ std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Polling
|
} // namespace Polling
|
||||||
|
== == == =
|
||||||
|
>>>>>>> 6e5fec9fe (add input common changes)
|
||||||
} // namespace InputCommon
|
} // namespace InputCommon
|
||||||
|
@ -1,72 +1,147 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
class ParamPackage;
|
class ParamPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Common::Input {
|
||||||
|
enum class ButtonNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Settings::NativeAnalog {
|
||||||
|
enum Values : int;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Settings::NativeButton {
|
||||||
|
enum Values : int;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Settings::NativeMotion {
|
||||||
|
enum Values : int;
|
||||||
|
}
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace InputCommon {
|
||||||
|
class Keyboard;
|
||||||
|
class Mouse;
|
||||||
|
class TouchScreen;
|
||||||
|
class VirtualGamepad;
|
||||||
|
struct MappingData;
|
||||||
|
} // namespace InputCommon
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
namespace Polling {
|
||||||
|
/// Type of input desired for mapping purposes
|
||||||
|
enum class InputType { None, Button, Stick, Motion, Touch };
|
||||||
|
} // namespace Polling
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
|
||||||
|
* mapping for the device.
|
||||||
|
*/
|
||||||
|
using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
|
||||||
|
using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
|
||||||
|
using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
|
||||||
|
|
||||||
|
class InputSubsystem {
|
||||||
|
public:
|
||||||
|
explicit InputSubsystem();
|
||||||
|
~InputSubsystem();
|
||||||
|
|
||||||
|
InputSubsystem(const InputSubsystem&) = delete;
|
||||||
|
InputSubsystem& operator=(const InputSubsystem&) = delete;
|
||||||
|
|
||||||
|
InputSubsystem(InputSubsystem&&) = delete;
|
||||||
|
InputSubsystem& operator=(InputSubsystem&&) = delete;
|
||||||
|
|
||||||
/// Initializes and registers all built-in input device factories.
|
/// Initializes and registers all built-in input device factories.
|
||||||
void Init();
|
void Initialize();
|
||||||
|
|
||||||
/// Deregisters all built-in input device factories and shuts them down.
|
/// Unregisters all built-in input device factories and shuts them down.
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
class Keyboard;
|
/// Retrieves the underlying keyboard device.
|
||||||
|
[[nodiscard]] Keyboard* GetKeyboard();
|
||||||
|
|
||||||
/// Gets the keyboard button device factory.
|
/// Retrieves the underlying keyboard device.
|
||||||
Keyboard* GetKeyboard();
|
[[nodiscard]] const Keyboard* GetKeyboard() const;
|
||||||
|
|
||||||
class MotionEmu;
|
/// Retrieves the underlying mouse device.
|
||||||
|
[[nodiscard]] Mouse* GetMouse();
|
||||||
|
|
||||||
/// Gets the motion emulation factory.
|
/// Retrieves the underlying mouse device.
|
||||||
MotionEmu* GetMotionEmu();
|
[[nodiscard]] const Mouse* GetMouse() const;
|
||||||
|
|
||||||
/// Generates a serialized param package for creating a keyboard button device
|
/// Retrieves the underlying touch screen device.
|
||||||
std::string GenerateKeyboardParam(int key_code);
|
[[nodiscard]] TouchScreen* GetTouchScreen();
|
||||||
|
|
||||||
/// Generates a serialized param package for creating an analog device taking input from keyboard
|
/// Retrieves the underlying touch screen device.
|
||||||
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
[[nodiscard]] const TouchScreen* GetTouchScreen() const;
|
||||||
int key_modifier, float modifier_scale);
|
|
||||||
|
|
||||||
Common::ParamPackage GetControllerButtonBinds(const Common::ParamPackage& params, int button);
|
/// Retrieves the underlying virtual gamepad input device.
|
||||||
Common::ParamPackage GetControllerAnalogBinds(const Common::ParamPackage& params, int analog);
|
[[nodiscard]] VirtualGamepad* GetVirtualGamepad();
|
||||||
|
|
||||||
/// Reloads the input devices
|
/// Retrieves the underlying virtual gamepad input device.
|
||||||
|
[[nodiscard]] const VirtualGamepad* GetVirtualGamepad() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all available input devices that this Factory can create a new device with.
|
||||||
|
* Each returned ParamPackage should have a `display` field used for display, a `engine` field
|
||||||
|
* for backends to determine if this backend is meant to service the request and any other
|
||||||
|
* information needed to identify this in the backend later.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
|
||||||
|
|
||||||
|
/// Retrieves the analog mappings for the given device.
|
||||||
|
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
|
||||||
|
|
||||||
|
/// Retrieves the button mappings for the given device.
|
||||||
|
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
|
||||||
|
|
||||||
|
/// Retrieves the motion mappings for the given device.
|
||||||
|
[[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
|
||||||
|
|
||||||
|
/// Returns an enum contaning the name to be displayed from the input engine.
|
||||||
|
[[nodiscard]] Common::Input::ButtonNames GetButtonName(
|
||||||
|
const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
/// Returns true if device is a controller.
|
||||||
|
[[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
|
||||||
|
|
||||||
|
/// Returns true if axis of a stick aren't mapped in the correct direction
|
||||||
|
[[nodiscard]] bool IsStickInverted(const Common::ParamPackage& device) const;
|
||||||
|
|
||||||
|
/// Reloads the input devices.
|
||||||
void ReloadInputDevices();
|
void ReloadInputDevices();
|
||||||
|
|
||||||
namespace Polling {
|
/// Start polling from all backends for a desired input type.
|
||||||
|
void BeginMapping(Polling::InputType type);
|
||||||
|
|
||||||
enum class DeviceType { Button, Analog };
|
/// Returns an input event with mapping information.
|
||||||
|
[[nodiscard]] Common::ParamPackage GetNextInput() const;
|
||||||
|
|
||||||
/**
|
/// Stop polling from all backends.
|
||||||
* A class that can be used to get inputs from an input device like controllers without having to
|
void StopMapping() const;
|
||||||
* poll the device's status yourself
|
|
||||||
*/
|
/// Signals SDL driver for new input events
|
||||||
class DevicePoller {
|
void PumpEvents() const;
|
||||||
public:
|
|
||||||
virtual ~DevicePoller() = default;
|
private:
|
||||||
/// Setup and start polling for inputs, should be called before GetNextInput
|
struct Impl;
|
||||||
virtual void Start() = 0;
|
std::unique_ptr<Impl> impl;
|
||||||
/// Stop polling
|
|
||||||
virtual void Stop() = 0;
|
|
||||||
/**
|
|
||||||
* Every call to this function returns the next input recorded since calling Start
|
|
||||||
* @return A ParamPackage of the recorded input, which can be used to create an InputDevice.
|
|
||||||
* If there has been no input, the package is empty
|
|
||||||
*/
|
|
||||||
virtual Common::ParamPackage GetNextInput() = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all DevicePoller from all backends for a specific device type
|
/// Generates a serialized param package for creating a keyboard button device.
|
||||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
|
std::string GenerateKeyboardParam(int key_code);
|
||||||
} // namespace Polling
|
|
||||||
|
/// Generates a serialized param package for creating an analog device taking input from keyboard.
|
||||||
|
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
||||||
|
int key_modifier, float modifier_scale);
|
||||||
} // namespace InputCommon
|
} // namespace InputCommon
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
#include <tuple>
|
|
||||||
#include "common/math_util.h"
|
|
||||||
#include "common/quaternion.h"
|
|
||||||
#include "common/thread.h"
|
|
||||||
#include "common/vector_math.h"
|
|
||||||
#include "input_common/motion_emu.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
// Implementation class of the motion emulation device
|
|
||||||
class MotionEmuDevice {
|
|
||||||
public:
|
|
||||||
MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp)
|
|
||||||
: update_millisecond(update_millisecond),
|
|
||||||
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
|
||||||
std::chrono::milliseconds(update_millisecond))),
|
|
||||||
sensitivity(sensitivity), tilt_clamp(tilt_clamp),
|
|
||||||
motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
|
|
||||||
|
|
||||||
~MotionEmuDevice() {
|
|
||||||
if (motion_emu_thread.joinable()) {
|
|
||||||
shutdown_event.Set();
|
|
||||||
motion_emu_thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BeginTilt(int x, int y) {
|
|
||||||
mouse_origin = Common::MakeVec(x, y);
|
|
||||||
is_tilting = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tilt(int x, int y) {
|
|
||||||
auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
|
|
||||||
if (is_tilting) {
|
|
||||||
std::lock_guard guard{tilt_mutex};
|
|
||||||
if (mouse_move.x == 0 && mouse_move.y == 0) {
|
|
||||||
tilt_angle = 0;
|
|
||||||
} else {
|
|
||||||
tilt_direction = mouse_move.Cast<float>();
|
|
||||||
tilt_angle = std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
|
|
||||||
Common::PI * this->tilt_clamp / 180.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndTilt() {
|
|
||||||
std::lock_guard guard{tilt_mutex};
|
|
||||||
tilt_angle = 0;
|
|
||||||
is_tilting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
|
|
||||||
std::lock_guard guard{status_mutex};
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const int update_millisecond;
|
|
||||||
const std::chrono::steady_clock::duration update_duration;
|
|
||||||
const float sensitivity;
|
|
||||||
|
|
||||||
Common::Vec2<int> mouse_origin;
|
|
||||||
|
|
||||||
std::mutex tilt_mutex;
|
|
||||||
Common::Vec2<float> tilt_direction;
|
|
||||||
float tilt_angle = 0;
|
|
||||||
float tilt_clamp = 90;
|
|
||||||
|
|
||||||
bool is_tilting = false;
|
|
||||||
|
|
||||||
Common::Event shutdown_event;
|
|
||||||
|
|
||||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
|
|
||||||
std::mutex status_mutex;
|
|
||||||
|
|
||||||
// Note: always keep the thread declaration at the end so that other objects are initialized
|
|
||||||
// before this!
|
|
||||||
std::thread motion_emu_thread;
|
|
||||||
|
|
||||||
void MotionEmuThread() {
|
|
||||||
auto update_time = std::chrono::steady_clock::now();
|
|
||||||
Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
|
|
||||||
Common::Quaternion<float> old_q;
|
|
||||||
|
|
||||||
while (!shutdown_event.WaitUntil(update_time)) {
|
|
||||||
update_time += update_duration;
|
|
||||||
old_q = q;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard guard{tilt_mutex};
|
|
||||||
|
|
||||||
// Find the quaternion describing current 3DS tilting
|
|
||||||
q = Common::MakeQuaternion(
|
|
||||||
Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inv_q = q.Inverse();
|
|
||||||
|
|
||||||
// Set the gravity vector in world space
|
|
||||||
auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f);
|
|
||||||
|
|
||||||
// Find the angular rate vector in world space
|
|
||||||
auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
|
|
||||||
angular_rate *= 1000 / update_millisecond / Common::PI * 180;
|
|
||||||
|
|
||||||
// Transform the two vectors from world space to 3DS space
|
|
||||||
gravity = QuaternionRotate(inv_q, gravity);
|
|
||||||
angular_rate = QuaternionRotate(inv_q, angular_rate);
|
|
||||||
|
|
||||||
// Update the sensor state
|
|
||||||
{
|
|
||||||
std::lock_guard guard{status_mutex};
|
|
||||||
status = std::make_tuple(gravity, angular_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
|
|
||||||
// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
|
|
||||||
// can forward all the inputs to the implementation only when it is valid.
|
|
||||||
class MotionEmuDeviceWrapper : public Input::MotionDevice {
|
|
||||||
public:
|
|
||||||
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) {
|
|
||||||
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity, tilt_clamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
|
|
||||||
return device->GetStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<MotionEmuDevice> device;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
|
|
||||||
int update_period = params.Get("update_period", 100);
|
|
||||||
float sensitivity = params.Get("sensitivity", 0.01f);
|
|
||||||
float tilt_clamp = params.Get("tilt_clamp", 90.0f);
|
|
||||||
auto device_wrapper =
|
|
||||||
std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity, tilt_clamp);
|
|
||||||
// Previously created device is disconnected here. Having two motion devices for 3DS is not
|
|
||||||
// expected.
|
|
||||||
current_device = device_wrapper->device;
|
|
||||||
return std::move(device_wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::BeginTilt(int x, int y) {
|
|
||||||
if (auto ptr = current_device.lock()) {
|
|
||||||
ptr->BeginTilt(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::Tilt(int x, int y) {
|
|
||||||
if (auto ptr = current_device.lock()) {
|
|
||||||
ptr->Tilt(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MotionEmu::EndTilt() {
|
|
||||||
if (auto ptr = current_device.lock()) {
|
|
||||||
ptr->EndTilt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,46 +0,0 @@
|
|||||||
// Copyright 2017 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "core/frontend/input.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
class MotionEmuDevice;
|
|
||||||
|
|
||||||
class MotionEmu : public Input::Factory<Input::MotionDevice> {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a motion device emulated from mouse input
|
|
||||||
* @param params contains parameters for creating the device:
|
|
||||||
* - "update_period": update period in milliseconds
|
|
||||||
* - "sensitivity": the coefficient converting mouse movement to tilting angle
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt has begun.
|
|
||||||
* @param x the x-coordinate of the cursor
|
|
||||||
* @param y the y-coordinate of the cursor
|
|
||||||
*/
|
|
||||||
void BeginTilt(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt is occurring.
|
|
||||||
* @param x the x-coordinate of the cursor
|
|
||||||
* @param y the y-coordinate of the cursor
|
|
||||||
*/
|
|
||||||
void Tilt(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that a motion sensor tilt has ended.
|
|
||||||
*/
|
|
||||||
void EndTilt();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::weak_ptr<MotionEmuDevice> current_device;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,19 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "input_common/sdl/sdl.h"
|
|
||||||
#ifdef HAVE_SDL2
|
|
||||||
#include "input_common/sdl/sdl_impl.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace InputCommon::SDL {
|
|
||||||
|
|
||||||
std::unique_ptr<State> Init() {
|
|
||||||
#ifdef HAVE_SDL2
|
|
||||||
return std::make_unique<SDLState>();
|
|
||||||
#else
|
|
||||||
return std::make_unique<NullState>();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
} // namespace InputCommon::SDL
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include "core/frontend/input.h"
|
|
||||||
#include "input_common/main.h"
|
|
||||||
|
|
||||||
union SDL_Event;
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
class ParamPackage;
|
|
||||||
} // namespace Common
|
|
||||||
|
|
||||||
namespace InputCommon::Polling {
|
|
||||||
class DevicePoller;
|
|
||||||
enum class DeviceType;
|
|
||||||
} // namespace InputCommon::Polling
|
|
||||||
|
|
||||||
namespace InputCommon::SDL {
|
|
||||||
|
|
||||||
class State {
|
|
||||||
public:
|
|
||||||
using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
|
|
||||||
|
|
||||||
/// Unregisters SDL device factories and shut them down.
|
|
||||||
virtual ~State() = default;
|
|
||||||
|
|
||||||
virtual Pollers GetPollers(Polling::DeviceType type) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NullState : public State {
|
|
||||||
public:
|
|
||||||
Pollers GetPollers(Polling::DeviceType type) override {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<State> Init();
|
|
||||||
|
|
||||||
} // namespace InputCommon::SDL
|
|
File diff suppressed because it is too large
Load Diff
@ -1,85 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "common/threadsafe_queue.h"
|
|
||||||
#include "input_common/sdl/sdl.h"
|
|
||||||
|
|
||||||
union SDL_Event;
|
|
||||||
using SDL_Joystick = struct _SDL_Joystick;
|
|
||||||
using SDL_JoystickID = s32;
|
|
||||||
using SDL_GameController = struct _SDL_GameController;
|
|
||||||
|
|
||||||
namespace InputCommon::SDL {
|
|
||||||
|
|
||||||
class SDLJoystick;
|
|
||||||
class SDLGameController;
|
|
||||||
class SDLButtonFactory;
|
|
||||||
class SDLAnalogFactory;
|
|
||||||
class SDLMotionFactory;
|
|
||||||
|
|
||||||
class SDLState : public State {
|
|
||||||
public:
|
|
||||||
/// Initializes and registers SDL device factories
|
|
||||||
SDLState();
|
|
||||||
|
|
||||||
/// Unregisters SDL device factories and shut them down.
|
|
||||||
~SDLState() override;
|
|
||||||
|
|
||||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
|
||||||
void HandleGameControllerEvent(const SDL_Event& event);
|
|
||||||
|
|
||||||
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
|
|
||||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
|
||||||
|
|
||||||
std::shared_ptr<SDLGameController> GetSDLGameControllerByGUID(const std::string& guid,
|
|
||||||
int port);
|
|
||||||
|
|
||||||
Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port,
|
|
||||||
Settings::NativeButton::Values button);
|
|
||||||
Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port,
|
|
||||||
Settings::NativeAnalog::Values analog);
|
|
||||||
|
|
||||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
|
||||||
Pollers GetPollers(Polling::DeviceType type) override;
|
|
||||||
|
|
||||||
/// Used by the Pollers during config
|
|
||||||
std::atomic<bool> polling = false;
|
|
||||||
Common::SPSCQueue<SDL_Event> event_queue;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void InitJoystick(int joystick_index);
|
|
||||||
void CloseJoystick(SDL_Joystick* sdl_joystick);
|
|
||||||
|
|
||||||
void InitGameController(int joystick_index);
|
|
||||||
void CloseGameController(SDL_GameController* sdl_controller);
|
|
||||||
|
|
||||||
/// Needs to be called before SDL_QuitSubSystem.
|
|
||||||
void CloseJoysticks();
|
|
||||||
void CloseGameControllers();
|
|
||||||
|
|
||||||
/// Map of GUID of a list of corresponding virtual Joysticks
|
|
||||||
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
|
|
||||||
std::mutex joystick_map_mutex;
|
|
||||||
|
|
||||||
/// Map of GUID of a list of corresponding virtual Controllers
|
|
||||||
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLGameController>>> controller_map;
|
|
||||||
std::mutex controller_map_mutex;
|
|
||||||
|
|
||||||
std::shared_ptr<SDLButtonFactory> button_factory;
|
|
||||||
std::shared_ptr<SDLAnalogFactory> analog_factory;
|
|
||||||
std::shared_ptr<SDLMotionFactory> motion_factory;
|
|
||||||
|
|
||||||
bool start_thread = false;
|
|
||||||
std::atomic<bool> initialized = false;
|
|
||||||
|
|
||||||
std::thread poll_thread;
|
|
||||||
};
|
|
||||||
} // namespace InputCommon::SDL
|
|
@ -1,49 +0,0 @@
|
|||||||
// Copyright 2020 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "core/3ds.h"
|
|
||||||
#include "input_common/touch_from_button.h"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
class TouchFromButtonDevice final : public Input::TouchDevice {
|
|
||||||
public:
|
|
||||||
TouchFromButtonDevice() {
|
|
||||||
for (const auto& config_entry :
|
|
||||||
Settings::values
|
|
||||||
.touch_from_button_maps[Settings::values.current_input_profile
|
|
||||||
.touch_from_button_map_index]
|
|
||||||
.buttons) {
|
|
||||||
|
|
||||||
const Common::ParamPackage package{config_entry};
|
|
||||||
map.emplace_back(Input::CreateDevice<Input::ButtonDevice>(config_entry),
|
|
||||||
std::clamp(package.Get("x", 0), 0, Core::kScreenBottomWidth),
|
|
||||||
std::clamp(package.Get("y", 0), 0, Core::kScreenBottomHeight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<float, float, bool> GetStatus() const override {
|
|
||||||
for (const auto& m : map) {
|
|
||||||
const bool state = std::get<0>(m)->GetStatus();
|
|
||||||
if (state) {
|
|
||||||
const float x = static_cast<float>(std::get<1>(m)) / Core::kScreenBottomWidth;
|
|
||||||
const float y = static_cast<float>(std::get<2>(m)) / Core::kScreenBottomHeight;
|
|
||||||
return {x, y, true};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// A vector of the mapped button, its x and its y-coordinate
|
|
||||||
std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
|
|
||||||
const Common::ParamPackage& params) {
|
|
||||||
return std::make_unique<TouchFromButtonDevice>();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,23 +0,0 @@
|
|||||||
// Copyright 2020 Citra 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"
|
|
||||||
|
|
||||||
namespace InputCommon {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A touch device factory that takes a list of button devices and combines them into a touch device.
|
|
||||||
*/
|
|
||||||
class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Creates a touch device from a list of button devices
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace InputCommon
|
|
@ -1,293 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstring>
|
|
||||||
#include <functional>
|
|
||||||
#include <thread>
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "input_common/udp/client.h"
|
|
||||||
#include "input_common/udp/protocol.h"
|
|
||||||
|
|
||||||
using boost::asio::ip::udp;
|
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
|
||||||
|
|
||||||
struct SocketCallback {
|
|
||||||
std::function<void(Response::Version)> version;
|
|
||||||
std::function<void(Response::PortInfo)> port_info;
|
|
||||||
std::function<void(Response::PadData)> pad_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Socket {
|
|
||||||
public:
|
|
||||||
using clock = std::chrono::system_clock;
|
|
||||||
|
|
||||||
explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
|
||||||
SocketCallback callback)
|
|
||||||
: callback(std::move(callback)), timer(io_service),
|
|
||||||
socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id),
|
|
||||||
pad_index(pad_index) {
|
|
||||||
boost::system::error_code ec{};
|
|
||||||
auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
|
|
||||||
if (ec.value() != boost::system::errc::success) {
|
|
||||||
LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
|
|
||||||
ipv4 = boost::asio::ip::address_v4{};
|
|
||||||
}
|
|
||||||
|
|
||||||
send_endpoint = {udp::endpoint(ipv4, port)};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Stop() {
|
|
||||||
io_service.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Loop() {
|
|
||||||
io_service.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void StartSend(const clock::time_point& from) {
|
|
||||||
timer.expires_at(from + std::chrono::seconds(3));
|
|
||||||
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void StartReceive() {
|
|
||||||
socket.async_receive_from(
|
|
||||||
boost::asio::buffer(receive_buffer), receive_endpoint,
|
|
||||||
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
|
|
||||||
HandleReceive(error, bytes_transferred);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
|
|
||||||
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
|
|
||||||
switch (*type) {
|
|
||||||
case Type::Version: {
|
|
||||||
Response::Version version;
|
|
||||||
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
|
|
||||||
callback.version(std::move(version));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Type::PortInfo: {
|
|
||||||
Response::PortInfo port_info;
|
|
||||||
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
|
|
||||||
sizeof(Response::PortInfo));
|
|
||||||
callback.port_info(std::move(port_info));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Type::PadData: {
|
|
||||||
Response::PadData pad_data;
|
|
||||||
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
|
|
||||||
callback.pad_data(std::move(pad_data));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StartReceive();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleSend(const boost::system::error_code& error) {
|
|
||||||
boost::system::error_code _ignored{};
|
|
||||||
// Send a request for getting port info for the pad
|
|
||||||
Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
|
|
||||||
const auto port_message = Request::Create(port_info, client_id);
|
|
||||||
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
|
|
||||||
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
|
|
||||||
|
|
||||||
// Send a request for getting pad data for the pad
|
|
||||||
Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
|
|
||||||
const auto pad_message = Request::Create(pad_data, client_id);
|
|
||||||
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
|
|
||||||
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
|
|
||||||
StartSend(timer.expiry());
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketCallback callback;
|
|
||||||
boost::asio::io_service io_service;
|
|
||||||
boost::asio::basic_waitable_timer<clock> timer;
|
|
||||||
udp::socket socket;
|
|
||||||
|
|
||||||
u32 client_id{};
|
|
||||||
u8 pad_index{};
|
|
||||||
|
|
||||||
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
|
|
||||||
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
|
|
||||||
std::array<u8, PORT_INFO_SIZE> send_buffer1;
|
|
||||||
std::array<u8, PAD_DATA_SIZE> send_buffer2;
|
|
||||||
udp::endpoint send_endpoint;
|
|
||||||
|
|
||||||
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
|
|
||||||
udp::endpoint receive_endpoint;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void SocketLoop(Socket* socket) {
|
|
||||||
socket->StartReceive();
|
|
||||||
socket->StartSend(Socket::clock::now());
|
|
||||||
socket->Loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
|
|
||||||
u8 pad_index, u32 client_id)
|
|
||||||
: status(std::move(status)) {
|
|
||||||
StartCommunication(host, port, pad_index, client_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Client::~Client() {
|
|
||||||
socket->Stop();
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
|
||||||
socket->Stop();
|
|
||||||
thread.join();
|
|
||||||
StartCommunication(host, port, pad_index, client_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::OnVersion(Response::Version data) {
|
|
||||||
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::OnPortInfo(Response::PortInfo data) {
|
|
||||||
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::OnPadData(Response::PadData data) {
|
|
||||||
LOG_TRACE(Input, "PadData packet received");
|
|
||||||
if (data.packet_counter <= packet_sequence) {
|
|
||||||
LOG_WARNING(
|
|
||||||
Input,
|
|
||||||
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
|
||||||
packet_sequence, data.packet_counter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
packet_sequence = data.packet_counter;
|
|
||||||
// Due to differences between the 3ds and cemuhookudp motion directions, we need to invert
|
|
||||||
// accel.x and accel.z and also invert pitch and yaw. See
|
|
||||||
// https://github.com/citra-emu/citra/pull/4049 for more details on gyro/accel
|
|
||||||
Common::Vec3f accel = Common::MakeVec<float>(-data.accel.x, data.accel.y, -data.accel.z);
|
|
||||||
Common::Vec3f gyro = Common::MakeVec<float>(-data.gyro.pitch, -data.gyro.yaw, data.gyro.roll);
|
|
||||||
{
|
|
||||||
std::lock_guard guard(status->update_mutex);
|
|
||||||
|
|
||||||
status->motion_status = {accel, gyro};
|
|
||||||
|
|
||||||
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
|
|
||||||
// between a simple "tap" and a hard press that causes the touch screen to click.
|
|
||||||
const bool is_active = data.touch_1.is_active != 0;
|
|
||||||
|
|
||||||
float x = 0;
|
|
||||||
float y = 0;
|
|
||||||
|
|
||||||
if (is_active && status->touch_calibration) {
|
|
||||||
const u16 min_x = status->touch_calibration->min_x;
|
|
||||||
const u16 max_x = status->touch_calibration->max_x;
|
|
||||||
const u16 min_y = status->touch_calibration->min_y;
|
|
||||||
const u16 max_y = status->touch_calibration->max_y;
|
|
||||||
|
|
||||||
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
|
|
||||||
static_cast<float>(max_x - min_x);
|
|
||||||
y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
|
|
||||||
static_cast<float>(max_y - min_y);
|
|
||||||
}
|
|
||||||
|
|
||||||
status->touch_status = {x, y, is_active};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
|
||||||
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
|
||||||
[this](Response::PortInfo info) { OnPortInfo(info); },
|
|
||||||
[this](Response::PadData data) { OnPadData(data); }};
|
|
||||||
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
|
||||||
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
|
|
||||||
thread = std::thread{SocketLoop, this->socket.get()};
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
|
||||||
const std::function<void()>& success_callback,
|
|
||||||
const std::function<void()>& failure_callback) {
|
|
||||||
std::thread([=] {
|
|
||||||
Common::Event success_event;
|
|
||||||
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
|
|
||||||
[&](Response::PadData data) { success_event.Set(); }};
|
|
||||||
Socket socket{host, port, pad_index, client_id, std::move(callback)};
|
|
||||||
std::thread worker_thread{SocketLoop, &socket};
|
|
||||||
bool result = success_event.WaitFor(std::chrono::seconds(8));
|
|
||||||
socket.Stop();
|
|
||||||
worker_thread.join();
|
|
||||||
if (result) {
|
|
||||||
success_callback();
|
|
||||||
} else {
|
|
||||||
failure_callback();
|
|
||||||
}
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
CalibrationConfigurationJob::CalibrationConfigurationJob(
|
|
||||||
const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
|
||||||
std::function<void(Status)> status_callback,
|
|
||||||
std::function<void(u16, u16, u16, u16)> data_callback) {
|
|
||||||
|
|
||||||
std::thread([=, this] {
|
|
||||||
u16 min_x{UINT16_MAX};
|
|
||||||
u16 min_y{UINT16_MAX};
|
|
||||||
u16 max_x{};
|
|
||||||
u16 max_y{};
|
|
||||||
|
|
||||||
Status current_status{Status::Initialized};
|
|
||||||
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
|
|
||||||
[&](Response::PadData data) {
|
|
||||||
constexpr u16 CALIBRATION_THRESHOLD = 100;
|
|
||||||
|
|
||||||
if (current_status == Status::Initialized) {
|
|
||||||
// Receiving data means the communication is ready now
|
|
||||||
current_status = Status::Ready;
|
|
||||||
status_callback(current_status);
|
|
||||||
}
|
|
||||||
if (!data.touch_1.is_active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
|
|
||||||
data.touch_1.y);
|
|
||||||
min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
|
|
||||||
min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
|
|
||||||
if (current_status == Status::Ready) {
|
|
||||||
// First touch - min data (min_x/min_y)
|
|
||||||
current_status = Status::Stage1Completed;
|
|
||||||
status_callback(current_status);
|
|
||||||
}
|
|
||||||
if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
|
|
||||||
data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
|
|
||||||
// Set the current position as max value and finishes
|
|
||||||
// configuration
|
|
||||||
max_x = data.touch_1.x;
|
|
||||||
max_y = data.touch_1.y;
|
|
||||||
current_status = Status::Completed;
|
|
||||||
data_callback(min_x, min_y, max_x, max_y);
|
|
||||||
status_callback(current_status);
|
|
||||||
|
|
||||||
complete_event.Set();
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
Socket socket{host, port, pad_index, client_id, std::move(callback)};
|
|
||||||
std::thread worker_thread{SocketLoop, &socket};
|
|
||||||
complete_event.Wait();
|
|
||||||
socket.Stop();
|
|
||||||
worker_thread.join();
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CalibrationConfigurationJob::Stop() {
|
|
||||||
complete_event.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
|
@ -1,95 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <tuple>
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "common/thread.h"
|
|
||||||
#include "common/vector_math.h"
|
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
|
||||||
|
|
||||||
constexpr u16 DEFAULT_PORT = 26760;
|
|
||||||
constexpr char DEFAULT_ADDR[] = "127.0.0.1";
|
|
||||||
|
|
||||||
class Socket;
|
|
||||||
|
|
||||||
namespace Response {
|
|
||||||
struct PadData;
|
|
||||||
struct PortInfo;
|
|
||||||
struct Version;
|
|
||||||
} // namespace Response
|
|
||||||
|
|
||||||
struct DeviceStatus {
|
|
||||||
std::mutex update_mutex;
|
|
||||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
|
|
||||||
std::tuple<float, float, bool> touch_status;
|
|
||||||
|
|
||||||
// calibration data for scaling the device's touch area to 3ds
|
|
||||||
struct CalibrationData {
|
|
||||||
u16 min_x{};
|
|
||||||
u16 min_y{};
|
|
||||||
u16 max_x{};
|
|
||||||
u16 max_y{};
|
|
||||||
};
|
|
||||||
std::optional<CalibrationData> touch_calibration;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Client {
|
|
||||||
public:
|
|
||||||
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
|
|
||||||
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
|
|
||||||
~Client();
|
|
||||||
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
|
|
||||||
u32 client_id = 24872);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void OnVersion(Response::Version);
|
|
||||||
void OnPortInfo(Response::PortInfo);
|
|
||||||
void OnPadData(Response::PadData);
|
|
||||||
void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
|
|
||||||
|
|
||||||
std::unique_ptr<Socket> socket;
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
std::thread thread;
|
|
||||||
u64 packet_sequence = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An async job allowing configuration of the touchpad calibration.
|
|
||||||
class CalibrationConfigurationJob {
|
|
||||||
public:
|
|
||||||
enum class Status {
|
|
||||||
Initialized,
|
|
||||||
Ready,
|
|
||||||
Stage1Completed,
|
|
||||||
Completed,
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Constructs and starts the job with the specified parameter.
|
|
||||||
*
|
|
||||||
* @param status_callback Callback for job status updates
|
|
||||||
* @param data_callback Called when calibration data is ready
|
|
||||||
*/
|
|
||||||
explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
|
|
||||||
u32 client_id, std::function<void(Status)> status_callback,
|
|
||||||
std::function<void(u16, u16, u16, u16)> data_callback);
|
|
||||||
~CalibrationConfigurationJob();
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Common::Event complete_event;
|
|
||||||
};
|
|
||||||
|
|
||||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
|
||||||
const std::function<void()>& success_callback,
|
|
||||||
const std::function<void()>& failure_callback);
|
|
||||||
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
|
@ -1,100 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <optional>
|
|
||||||
#include <tuple>
|
|
||||||
#include "common/param_package.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "core/frontend/input.h"
|
|
||||||
#include "input_common/udp/client.h"
|
|
||||||
#include "input_common/udp/udp.h"
|
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
|
||||||
|
|
||||||
class UDPTouchDevice final : public Input::TouchDevice {
|
|
||||||
public:
|
|
||||||
explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
|
||||||
std::tuple<float, float, bool> GetStatus() const override {
|
|
||||||
std::lock_guard guard(status->update_mutex);
|
|
||||||
return status->touch_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UDPMotionDevice final : public Input::MotionDevice {
|
|
||||||
public:
|
|
||||||
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
|
||||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
|
|
||||||
std::lock_guard guard(status->update_mutex);
|
|
||||||
return status->motion_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
|
|
||||||
public:
|
|
||||||
explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
|
||||||
|
|
||||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
|
|
||||||
{
|
|
||||||
std::lock_guard guard(status->update_mutex);
|
|
||||||
status->touch_calibration = DeviceStatus::CalibrationData{};
|
|
||||||
// These default values work well for DS4 but probably not other touch inputs
|
|
||||||
status->touch_calibration->min_x = params.Get("min_x", 100);
|
|
||||||
status->touch_calibration->min_y = params.Get("min_y", 50);
|
|
||||||
status->touch_calibration->max_x = params.Get("max_x", 1800);
|
|
||||||
status->touch_calibration->max_y = params.Get("max_y", 850);
|
|
||||||
}
|
|
||||||
return std::make_unique<UDPTouchDevice>(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
|
||||||
public:
|
|
||||||
explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
|
||||||
|
|
||||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
|
|
||||||
return std::make_unique<UDPMotionDevice>(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
State::State() {
|
|
||||||
auto status = std::make_shared<DeviceStatus>();
|
|
||||||
client =
|
|
||||||
std::make_unique<Client>(status, Settings::values.current_input_profile.udp_input_address,
|
|
||||||
Settings::values.current_input_profile.udp_input_port,
|
|
||||||
Settings::values.current_input_profile.udp_pad_index);
|
|
||||||
|
|
||||||
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
|
|
||||||
std::make_shared<UDPTouchFactory>(status));
|
|
||||||
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
|
|
||||||
std::make_shared<UDPMotionFactory>(status));
|
|
||||||
}
|
|
||||||
|
|
||||||
State::~State() {
|
|
||||||
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
|
|
||||||
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
|
|
||||||
}
|
|
||||||
|
|
||||||
void State::ReloadUDPClient() {
|
|
||||||
client->ReloadSocket(Settings::values.current_input_profile.udp_input_address,
|
|
||||||
Settings::values.current_input_profile.udp_input_port,
|
|
||||||
Settings::values.current_input_profile.udp_pad_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<State> Init() {
|
|
||||||
return std::make_unique<State>();
|
|
||||||
}
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
|
@ -1,24 +0,0 @@
|
|||||||
// Copyright 2018 Citra Emulator Project
|
|
||||||
// Licensed under GPLv2 or any later version
|
|
||||||
// Refer to the license.txt file included.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include "input_common/udp/client.h"
|
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
|
||||||
|
|
||||||
class State {
|
|
||||||
public:
|
|
||||||
State();
|
|
||||||
~State();
|
|
||||||
void ReloadUDPClient();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<Client> client;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<State> Init();
|
|
||||||
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
|
Reference in New Issue
Block a user