remove old files
This commit is contained in:
@@ -1,238 +0,0 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
#include "common/math_util.h"
|
||||
#include "common/settings.h"
|
||||
#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_, 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_) {
|
||||
Input::InputCallback<bool> callbacks{
|
||||
[this]([[maybe_unused]] bool status) { UpdateStatus(); }};
|
||||
up->SetCallback(callbacks);
|
||||
down->SetCallback(callbacks);
|
||||
left->SetCallback(callbacks);
|
||||
right->SetCallback(callbacks);
|
||||
modifier->SetCallback(callbacks);
|
||||
}
|
||||
|
||||
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 UpdateStatus() {
|
||||
const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
|
||||
|
||||
bool r = right->GetStatus();
|
||||
bool l = left->GetStatus();
|
||||
bool u = up->GetStatus();
|
||||
bool d = down->GetStatus();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetStatus() const override {
|
||||
if (Settings::values.emulate_analog_keyboard) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
float angle_ = GetAngle(now);
|
||||
return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude);
|
||||
}
|
||||
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;
|
||||
}
|
||||
const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
|
||||
return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
|
||||
static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
|
||||
}
|
||||
|
||||
Input::AnalogProperties GetAnalogProperties() const override {
|
||||
return {modifier_scale, 1.0f, 0.5f};
|
||||
}
|
||||
|
||||
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
|
||||
switch (direction) {
|
||||
case Input::AnalogDirection::RIGHT:
|
||||
return right->GetStatus();
|
||||
case Input::AnalogDirection::LEFT:
|
||||
return left->GetStatus();
|
||||
case Input::AnalogDirection::UP:
|
||||
return up->GetStatus();
|
||||
case Input::AnalogDirection::DOWN:
|
||||
return down->GetStatus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
Button up;
|
||||
Button down;
|
||||
Button left;
|
||||
Button right;
|
||||
Button modifier;
|
||||
float modifier_scale;
|
||||
float modifier_angle;
|
||||
float angle{};
|
||||
float goal_angle{};
|
||||
float amplitude{};
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
};
|
||||
|
||||
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);
|
||||
auto modifier_angle = params.Get("modifier_angle", 5.5f);
|
||||
return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
|
||||
std::move(right), std::move(modifier), modifier_scale,
|
||||
modifier_angle);
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,31 +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 {
|
||||
|
||||
/**
|
||||
* An analog device factory that takes direction button devices and combines them into a analog
|
||||
* device.
|
||||
*/
|
||||
class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates an analog device from direction button devices
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "up": a serialized ParamPackage for creating a button device for up direction
|
||||
* - "down": a serialized ParamPackage for creating a button device for down direction
|
||||
* - "left": a serialized ParamPackage for creating a button device for left direction
|
||||
* - "right": a serialized ParamPackage for creating a button device for right direction
|
||||
* - "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
|
||||
*/
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,506 +0,0 @@
|
||||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <libusb.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings_input.h"
|
||||
#include "input_common/gcadapter/gc_adapter.h"
|
||||
|
||||
namespace GCAdapter {
|
||||
|
||||
Adapter::Adapter() {
|
||||
if (usb_adapter_handle != nullptr) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO(Input, "GC Adapter Initialization started");
|
||||
|
||||
const int init_res = libusb_init(&libusb_ctx);
|
||||
if (init_res == LIBUSB_SUCCESS) {
|
||||
adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
|
||||
} else {
|
||||
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
|
||||
}
|
||||
}
|
||||
|
||||
Adapter::~Adapter() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
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);
|
||||
UpdateVibrations();
|
||||
}
|
||||
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) {
|
||||
UpdateYuzuSettings(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].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++;
|
||||
}
|
||||
pads[port].axis_values[index] =
|
||||
static_cast<s16>(axis_value - pads[port].axis_origin[index]);
|
||||
}
|
||||
}
|
||||
|
||||
void Adapter::UpdateYuzuSettings(std::size_t port) {
|
||||
if (port >= pads.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr u8 axis_threshold = 50;
|
||||
GCPadStatus pad_status = {.port = 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::UpdateVibrations() {
|
||||
// Use 8 states to keep the switching between on/off fast enough for
|
||||
// a human to not notice the difference between switching from on/off
|
||||
// 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 Adapter::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, output_endpoint, payload.data(),
|
||||
static_cast<s32>(payload.size()), &size, 16);
|
||||
if (err) {
|
||||
LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
|
||||
if (output_error_counter++ > 5) {
|
||||
LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
|
||||
rumble_enabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
output_error_counter = 0;
|
||||
vibration_changed = false;
|
||||
}
|
||||
|
||||
bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
|
||||
pads[port].rumble_amplitude = amplitude;
|
||||
|
||||
return rumble_enabled;
|
||||
}
|
||||
|
||||
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;
|
||||
rumble_enabled = true;
|
||||
input_error_counter = 0;
|
||||
output_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].enable_vibration = false;
|
||||
pads[port].rumble_amplitude = 0;
|
||||
pads[port].buttons = 0;
|
||||
pads[port].last_button = PadButton::Undefined;
|
||||
pads[port].axis_values.fill(0);
|
||||
pads[port].reset_origin_counter = 0;
|
||||
}
|
||||
|
||||
void Adapter::Reset() {
|
||||
JoinThreads();
|
||||
ClearLibusbHandle();
|
||||
ResetDevices();
|
||||
|
||||
if (libusb_ctx) {
|
||||
libusb_exit(libusb_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
InputCommon::ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
|
||||
Common::ParamPackage button_params({{"engine", "gcpad"}});
|
||||
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::pair<Settings::NativeButton::Values, PadAxes>, 2>
|
||||
switch_to_gcadapter_axis = {
|
||||
std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
|
||||
{Settings::NativeButton::ZR, PadAxes::TriggerRight},
|
||||
};
|
||||
for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
|
||||
Common::ParamPackage button_params({{"engine", "gcpad"}});
|
||||
button_params.Set("port", params.Get("port", 0));
|
||||
button_params.Set("button", static_cast<s32>(PadButton::Stick));
|
||||
button_params.Set("axis", static_cast<s32>(gcadapter_axis));
|
||||
button_params.Set("threshold", 0.5f);
|
||||
button_params.Set("direction", "+");
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
InputCommon::AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params;
|
||||
left_analog_params.Set("engine", "gcpad");
|
||||
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", "gcpad");
|
||||
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;
|
||||
}
|
||||
|
||||
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,168 +0,0 @@
|
||||
// Copyright 2014 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "common/common_types.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
struct libusb_context;
|
||||
struct libusb_device;
|
||||
struct libusb_device_handle;
|
||||
|
||||
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{};
|
||||
bool enable_vibration{};
|
||||
u8 rumble_amplitude{};
|
||||
u16 buttons{};
|
||||
PadButton last_button{};
|
||||
std::array<s16, 6> axis_values{};
|
||||
std::array<u8, 6> axis_origin{};
|
||||
u8 reset_origin_counter{};
|
||||
};
|
||||
|
||||
class Adapter {
|
||||
public:
|
||||
Adapter();
|
||||
~Adapter();
|
||||
|
||||
/// Request a vibration for a controller
|
||||
bool RumblePlay(std::size_t port, u8 amplitude);
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const;
|
||||
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
|
||||
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
|
||||
|
||||
private:
|
||||
using AdapterPayload = std::array<u8, 37>;
|
||||
|
||||
void UpdatePadType(std::size_t port, ControllerTypes pad_type);
|
||||
void UpdateControllers(const AdapterPayload& adapter_payload);
|
||||
void UpdateYuzuSettings(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 UpdateVibrations();
|
||||
|
||||
void AdapterInputThread();
|
||||
|
||||
void AdapterScanThread();
|
||||
|
||||
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
|
||||
|
||||
// Updates vibration state of all controllers
|
||||
void SendVibrations();
|
||||
|
||||
/// 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);
|
||||
|
||||
/// For shutting down, clear all data, join all threads, release usb
|
||||
void Reset();
|
||||
|
||||
// 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};
|
||||
u8 output_error_counter{0};
|
||||
int vibration_counter{0};
|
||||
|
||||
bool configuring{false};
|
||||
bool rumble_enabled{true};
|
||||
bool vibration_changed{true};
|
||||
};
|
||||
} // namespace GCAdapter
|
@@ -1,356 +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 {
|
||||
|
||||
class GCButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit GCButton(u32 port_, s32 button_, const 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 u32 port;
|
||||
const s32 button;
|
||||
const GCAdapter::Adapter* gcadapter;
|
||||
};
|
||||
|
||||
class GCAxisButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
|
||||
const 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) {
|
||||
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
|
||||
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
|
||||
return axis_value > threshold;
|
||||
}
|
||||
return axis_value < -threshold;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const 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 auto button_id = params.Get("button", 0);
|
||||
const auto port = static_cast<u32>(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());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Common::ParamPackage GCButtonFactory::GetNextInput() const {
|
||||
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;
|
||||
}
|
||||
|
||||
void GCButtonFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
adapter->BeginConfiguration();
|
||||
}
|
||||
|
||||
void GCButtonFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
adapter->EndConfiguration();
|
||||
}
|
||||
|
||||
class GCAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
|
||||
float deadzone_, float range_, const GCAdapter::Adapter* adapter)
|
||||
: port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
|
||||
deadzone(deadzone_), range(range_), 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) / (100.0f * range);
|
||||
}
|
||||
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);
|
||||
if (invert_x) {
|
||||
x = -x;
|
||||
}
|
||||
if (invert_y) {
|
||||
y = -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};
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetRawStatus() const override {
|
||||
const float x = GetAxis(axis_x);
|
||||
const float y = GetAxis(axis_y);
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
Input::AnalogProperties GetAnalogProperties() const override {
|
||||
return {deadzone, range, 0.5f};
|
||||
}
|
||||
|
||||
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
|
||||
const auto [x, y] = GetStatus();
|
||||
const float directional_deadzone = 0.5f;
|
||||
switch (direction) {
|
||||
case Input::AnalogDirection::RIGHT:
|
||||
return x > directional_deadzone;
|
||||
case Input::AnalogDirection::LEFT:
|
||||
return x < -directional_deadzone;
|
||||
case Input::AnalogDirection::UP:
|
||||
return y > directional_deadzone;
|
||||
case Input::AnalogDirection::DOWN:
|
||||
return y < -directional_deadzone;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 port;
|
||||
const u32 axis_x;
|
||||
const u32 axis_y;
|
||||
const bool invert_x;
|
||||
const bool invert_y;
|
||||
const float deadzone;
|
||||
const float range;
|
||||
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);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
|
||||
const std::string invert_x_value = params.Get("invert_x", "+");
|
||||
const std::string invert_y_value = params.Get("invert_y", "+");
|
||||
const bool invert_x = invert_x_value == "-";
|
||||
const bool invert_y = invert_y_value == "-";
|
||||
|
||||
return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
|
||||
adapter.get());
|
||||
}
|
||||
|
||||
void GCAnalogFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
adapter->BeginConfiguration();
|
||||
}
|
||||
|
||||
void GCAnalogFactory::EndConfiguration() {
|
||||
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);
|
||||
params.Set("invert_x", "+");
|
||||
params.Set("invert_y", "+");
|
||||
analog_x_axis = -1;
|
||||
analog_y_axis = -1;
|
||||
controller_number = -1;
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
class GCVibration final : public Input::VibrationDevice {
|
||||
public:
|
||||
explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
|
||||
: port(port_), gcadapter(adapter) {}
|
||||
|
||||
u8 GetStatus() const override {
|
||||
return gcadapter->RumblePlay(port, 0);
|
||||
}
|
||||
|
||||
bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
|
||||
[[maybe_unused]] f32 freq_high) const override {
|
||||
const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
|
||||
const auto processed_amplitude =
|
||||
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
|
||||
|
||||
return gcadapter->RumblePlay(port, processed_amplitude);
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 port;
|
||||
GCAdapter::Adapter* gcadapter;
|
||||
};
|
||||
|
||||
/// An vibration device factory that creates vibration devices from GC Adapter
|
||||
GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
|
||||
: adapter(std::move(adapter_)) {}
|
||||
|
||||
/**
|
||||
* Creates a vibration device from a joystick
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "port": the nth gcpad on the adapter
|
||||
*/
|
||||
std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto port = static_cast<u32>(params.Get("port", 0));
|
||||
|
||||
return std::make_unique<GCVibration>(port, adapter.get());
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,78 +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 "core/frontend/input.h"
|
||||
#include "input_common/gcadapter/gc_adapter.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:
|
||||
explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
|
||||
|
||||
/**
|
||||
* Creates a button device from a button press
|
||||
* @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;
|
||||
|
||||
Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
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:
|
||||
explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
|
||||
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
||||
Common::ParamPackage GetNextInput();
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/// A vibration device factory creates vibration devices from GC Adapter
|
||||
class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
|
||||
public:
|
||||
explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
|
||||
|
||||
std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<GCAdapter::Adapter> adapter;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,121 +0,0 @@
|
||||
// Copyright 2017 Citra 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 "input_common/keyboard.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class KeyButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_, bool toggle_)
|
||||
: key_button_list(std::move(key_button_list_)), toggle(toggle_) {}
|
||||
|
||||
~KeyButton() override;
|
||||
|
||||
bool GetStatus() const override {
|
||||
if (toggle) {
|
||||
return toggled_status.load(std::memory_order_relaxed);
|
||||
}
|
||||
return status.load();
|
||||
}
|
||||
|
||||
void ToggleButton() {
|
||||
if (lock) {
|
||||
return;
|
||||
}
|
||||
lock = true;
|
||||
const bool old_toggle_status = toggled_status.load();
|
||||
toggled_status.store(!old_toggle_status);
|
||||
}
|
||||
|
||||
void UnlockButton() {
|
||||
lock = false;
|
||||
}
|
||||
|
||||
friend class KeyButtonList;
|
||||
|
||||
private:
|
||||
std::shared_ptr<KeyButtonList> key_button_list;
|
||||
std::atomic<bool> status{false};
|
||||
std::atomic<bool> toggled_status{false};
|
||||
bool lock{false};
|
||||
const bool toggle;
|
||||
};
|
||||
|
||||
struct KeyButtonPair {
|
||||
int key_code;
|
||||
KeyButton* key_button;
|
||||
};
|
||||
|
||||
class KeyButtonList {
|
||||
public:
|
||||
void AddKeyButton(int key_code, KeyButton* key_button) {
|
||||
std::lock_guard guard{mutex};
|
||||
list.push_back(KeyButtonPair{key_code, key_button});
|
||||
}
|
||||
|
||||
void RemoveKeyButton(const KeyButton* key_button) {
|
||||
std::lock_guard guard{mutex};
|
||||
list.remove_if(
|
||||
[key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
|
||||
}
|
||||
|
||||
void ChangeKeyStatus(int key_code, bool pressed) {
|
||||
std::lock_guard guard{mutex};
|
||||
for (const KeyButtonPair& pair : list) {
|
||||
if (pair.key_code == key_code) {
|
||||
pair.key_button->status.store(pressed);
|
||||
if (pressed) {
|
||||
pair.key_button->ToggleButton();
|
||||
} else {
|
||||
pair.key_button->UnlockButton();
|
||||
}
|
||||
pair.key_button->TriggerOnChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeAllKeyStatus(bool pressed) {
|
||||
std::lock_guard guard{mutex};
|
||||
for (const KeyButtonPair& pair : list) {
|
||||
pair.key_button->status.store(pressed);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::list<KeyButtonPair> list;
|
||||
};
|
||||
|
||||
Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
|
||||
|
||||
KeyButton::~KeyButton() {
|
||||
key_button_list->RemoveKeyButton(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
|
||||
const int key_code = params.Get("code", 0);
|
||||
const bool toggle = params.Get("toggle", false);
|
||||
std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list, toggle);
|
||||
key_button_list->AddKeyButton(key_code, button.get());
|
||||
return button;
|
||||
}
|
||||
|
||||
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,34 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "input_common/motion_from_button.h"
|
||||
#include "input_common/motion_input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class MotionKey final : public Input::MotionDevice {
|
||||
public:
|
||||
using Button = std::unique_ptr<Input::ButtonDevice>;
|
||||
|
||||
explicit MotionKey(Button key_) : key(std::move(key_)) {}
|
||||
|
||||
Input::MotionStatus GetStatus() const override {
|
||||
|
||||
if (key->GetStatus()) {
|
||||
return motion.GetRandomMotion(2, 6);
|
||||
}
|
||||
return motion.GetRandomMotion(0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
Button key;
|
||||
InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
|
||||
std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
|
||||
auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
|
||||
return std::make_unique<MotionKey>(std::move(key));
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,25 +0,0 @@
|
||||
// Copyright 2020 yuzu 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 {
|
||||
|
||||
/**
|
||||
* An motion device factory that takes a keyboard button and uses it as a random
|
||||
* motion device.
|
||||
*/
|
||||
class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
|
||||
public:
|
||||
/**
|
||||
* Creates an motion device from button devices
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "key": a serialized ParamPackage for creating a button device
|
||||
*/
|
||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,307 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#include <random>
|
||||
#include "common/math_util.h"
|
||||
#include "input_common/motion_input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
|
||||
|
||||
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
|
||||
accel = acceleration;
|
||||
}
|
||||
|
||||
void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
|
||||
gyro = gyroscope - gyro_drift;
|
||||
|
||||
// Auto adjust drift to minimize drift
|
||||
if (!IsMoving(0.1f)) {
|
||||
gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
|
||||
}
|
||||
|
||||
if (gyro.Length2() < gyro_threshold) {
|
||||
gyro = {};
|
||||
} else {
|
||||
only_accelerometer = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
|
||||
quat = quaternion;
|
||||
}
|
||||
|
||||
void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
|
||||
gyro_drift = drift;
|
||||
}
|
||||
|
||||
void MotionInput::SetGyroThreshold(f32 threshold) {
|
||||
gyro_threshold = threshold;
|
||||
}
|
||||
|
||||
void MotionInput::EnableReset(bool reset) {
|
||||
reset_enabled = reset;
|
||||
}
|
||||
|
||||
void MotionInput::ResetRotations() {
|
||||
rotations = {};
|
||||
}
|
||||
|
||||
bool MotionInput::IsMoving(f32 sensitivity) const {
|
||||
return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
|
||||
}
|
||||
|
||||
bool MotionInput::IsCalibrated(f32 sensitivity) const {
|
||||
return real_error.Length() < sensitivity;
|
||||
}
|
||||
|
||||
void MotionInput::UpdateRotation(u64 elapsed_time) {
|
||||
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
|
||||
if (sample_period > 0.1f) {
|
||||
return;
|
||||
}
|
||||
rotations += gyro * sample_period;
|
||||
}
|
||||
|
||||
void MotionInput::UpdateOrientation(u64 elapsed_time) {
|
||||
if (!IsCalibrated(0.1f)) {
|
||||
ResetOrientation();
|
||||
}
|
||||
// Short name local variable for readability
|
||||
f32 q1 = quat.w;
|
||||
f32 q2 = quat.xyz[0];
|
||||
f32 q3 = quat.xyz[1];
|
||||
f32 q4 = quat.xyz[2];
|
||||
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
|
||||
|
||||
// Ignore invalid elapsed time
|
||||
if (sample_period > 0.1f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto normal_accel = accel.Normalized();
|
||||
auto rad_gyro = gyro * Common::PI * 2;
|
||||
const f32 swap = rad_gyro.x;
|
||||
rad_gyro.x = rad_gyro.y;
|
||||
rad_gyro.y = -swap;
|
||||
rad_gyro.z = -rad_gyro.z;
|
||||
|
||||
// Clear gyro values if there is no gyro present
|
||||
if (only_accelerometer) {
|
||||
rad_gyro.x = 0;
|
||||
rad_gyro.y = 0;
|
||||
rad_gyro.z = 0;
|
||||
}
|
||||
|
||||
// Ignore drift correction if acceleration is not reliable
|
||||
if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
|
||||
const f32 ax = -normal_accel.x;
|
||||
const f32 ay = normal_accel.y;
|
||||
const f32 az = -normal_accel.z;
|
||||
|
||||
// Estimated direction of gravity
|
||||
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
|
||||
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
|
||||
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
|
||||
|
||||
// Error is cross product between estimated direction and measured direction of gravity
|
||||
const Common::Vec3f new_real_error = {
|
||||
az * vx - ax * vz,
|
||||
ay * vz - az * vy,
|
||||
ax * vy - ay * vx,
|
||||
};
|
||||
|
||||
derivative_error = new_real_error - real_error;
|
||||
real_error = new_real_error;
|
||||
|
||||
// Prevent integral windup
|
||||
if (ki != 0.0f && !IsCalibrated(0.05f)) {
|
||||
integral_error += real_error;
|
||||
} else {
|
||||
integral_error = {};
|
||||
}
|
||||
|
||||
// Apply feedback terms
|
||||
if (!only_accelerometer) {
|
||||
rad_gyro += kp * real_error;
|
||||
rad_gyro += ki * integral_error;
|
||||
rad_gyro += kd * derivative_error;
|
||||
} else {
|
||||
// Give more weight to accelerometer values to compensate for the lack of gyro
|
||||
rad_gyro += 35.0f * kp * real_error;
|
||||
rad_gyro += 10.0f * ki * integral_error;
|
||||
rad_gyro += 10.0f * kd * derivative_error;
|
||||
|
||||
// Emulate gyro values for games that need them
|
||||
gyro.x = -rad_gyro.y;
|
||||
gyro.y = rad_gyro.x;
|
||||
gyro.z = -rad_gyro.z;
|
||||
UpdateRotation(elapsed_time);
|
||||
}
|
||||
}
|
||||
|
||||
const f32 gx = rad_gyro.y;
|
||||
const f32 gy = rad_gyro.x;
|
||||
const f32 gz = rad_gyro.z;
|
||||
|
||||
// Integrate rate of change of quaternion
|
||||
const f32 pa = q2;
|
||||
const f32 pb = q3;
|
||||
const f32 pc = q4;
|
||||
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
|
||||
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
|
||||
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
|
||||
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
|
||||
|
||||
quat.w = q1;
|
||||
quat.xyz[0] = q2;
|
||||
quat.xyz[1] = q3;
|
||||
quat.xyz[2] = q4;
|
||||
quat = quat.Normalized();
|
||||
}
|
||||
|
||||
std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
|
||||
const Common::Quaternion<float> quad{
|
||||
.xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
|
||||
.w = -quat.xyz[2],
|
||||
};
|
||||
const std::array<float, 16> matrix4x4 = quad.ToMatrix();
|
||||
|
||||
return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
|
||||
Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
|
||||
Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
|
||||
}
|
||||
|
||||
Common::Vec3f MotionInput::GetAcceleration() const {
|
||||
return accel;
|
||||
}
|
||||
|
||||
Common::Vec3f MotionInput::GetGyroscope() const {
|
||||
return gyro;
|
||||
}
|
||||
|
||||
Common::Quaternion<f32> MotionInput::GetQuaternion() const {
|
||||
return quat;
|
||||
}
|
||||
|
||||
Common::Vec3f MotionInput::GetRotations() const {
|
||||
return rotations;
|
||||
}
|
||||
|
||||
Input::MotionStatus MotionInput::GetMotion() const {
|
||||
const Common::Vec3f gyroscope = GetGyroscope();
|
||||
const Common::Vec3f accelerometer = GetAcceleration();
|
||||
const Common::Vec3f rotation = GetRotations();
|
||||
const std::array<Common::Vec3f, 3> orientation = GetOrientation();
|
||||
const Common::Quaternion<f32> quaternion = GetQuaternion();
|
||||
return {accelerometer, gyroscope, rotation, orientation, quaternion};
|
||||
}
|
||||
|
||||
Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<s16> distribution(-1000, 1000);
|
||||
const Common::Vec3f gyroscope{
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
};
|
||||
const Common::Vec3f accelerometer{
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
static_cast<f32>(distribution(gen)) * 0.001f,
|
||||
};
|
||||
constexpr Common::Vec3f rotation;
|
||||
constexpr std::array orientation{
|
||||
Common::Vec3f{1.0f, 0.0f, 0.0f},
|
||||
Common::Vec3f{0.0f, 1.0f, 0.0f},
|
||||
Common::Vec3f{0.0f, 0.0f, 1.0f},
|
||||
};
|
||||
constexpr Common::Quaternion<f32> quaternion{
|
||||
{0.0f, 0.0f, 0.0f},
|
||||
1.0f,
|
||||
};
|
||||
return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
|
||||
quaternion};
|
||||
}
|
||||
|
||||
void MotionInput::ResetOrientation() {
|
||||
if (!reset_enabled || only_accelerometer) {
|
||||
return;
|
||||
}
|
||||
if (!IsMoving(0.5f) && accel.z <= -0.9f) {
|
||||
++reset_counter;
|
||||
if (reset_counter > 900) {
|
||||
quat.w = 0;
|
||||
quat.xyz[0] = 0;
|
||||
quat.xyz[1] = 0;
|
||||
quat.xyz[2] = -1;
|
||||
SetOrientationFromAccelerometer();
|
||||
integral_error = {};
|
||||
reset_counter = 0;
|
||||
}
|
||||
} else {
|
||||
reset_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MotionInput::SetOrientationFromAccelerometer() {
|
||||
int iterations = 0;
|
||||
const f32 sample_period = 0.015f;
|
||||
|
||||
const auto normal_accel = accel.Normalized();
|
||||
|
||||
while (!IsCalibrated(0.01f) && ++iterations < 100) {
|
||||
// Short name local variable for readability
|
||||
f32 q1 = quat.w;
|
||||
f32 q2 = quat.xyz[0];
|
||||
f32 q3 = quat.xyz[1];
|
||||
f32 q4 = quat.xyz[2];
|
||||
|
||||
Common::Vec3f rad_gyro;
|
||||
const f32 ax = -normal_accel.x;
|
||||
const f32 ay = normal_accel.y;
|
||||
const f32 az = -normal_accel.z;
|
||||
|
||||
// Estimated direction of gravity
|
||||
const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
|
||||
const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
|
||||
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
|
||||
|
||||
// Error is cross product between estimated direction and measured direction of gravity
|
||||
const Common::Vec3f new_real_error = {
|
||||
az * vx - ax * vz,
|
||||
ay * vz - az * vy,
|
||||
ax * vy - ay * vx,
|
||||
};
|
||||
|
||||
derivative_error = new_real_error - real_error;
|
||||
real_error = new_real_error;
|
||||
|
||||
rad_gyro += 10.0f * kp * real_error;
|
||||
rad_gyro += 5.0f * ki * integral_error;
|
||||
rad_gyro += 10.0f * kd * derivative_error;
|
||||
|
||||
const f32 gx = rad_gyro.y;
|
||||
const f32 gy = rad_gyro.x;
|
||||
const f32 gz = rad_gyro.z;
|
||||
|
||||
// Integrate rate of change of quaternion
|
||||
const f32 pa = q2;
|
||||
const f32 pb = q3;
|
||||
const f32 pc = q4;
|
||||
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
|
||||
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
|
||||
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
|
||||
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
|
||||
|
||||
quat.w = q1;
|
||||
quat.xyz[0] = q2;
|
||||
quat.xyz[1] = q3;
|
||||
quat.xyz[2] = q4;
|
||||
quat = quat.Normalized();
|
||||
}
|
||||
}
|
||||
} // namespace InputCommon
|
@@ -1,74 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/quaternion.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/frontend/input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class MotionInput {
|
||||
public:
|
||||
explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
|
||||
|
||||
MotionInput(const MotionInput&) = default;
|
||||
MotionInput& operator=(const MotionInput&) = default;
|
||||
|
||||
MotionInput(MotionInput&&) = default;
|
||||
MotionInput& operator=(MotionInput&&) = default;
|
||||
|
||||
void SetAcceleration(const Common::Vec3f& acceleration);
|
||||
void SetGyroscope(const Common::Vec3f& gyroscope);
|
||||
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
|
||||
void SetGyroDrift(const Common::Vec3f& drift);
|
||||
void SetGyroThreshold(f32 threshold);
|
||||
|
||||
void EnableReset(bool reset);
|
||||
void ResetRotations();
|
||||
|
||||
void UpdateRotation(u64 elapsed_time);
|
||||
void UpdateOrientation(u64 elapsed_time);
|
||||
|
||||
[[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
|
||||
[[nodiscard]] Common::Vec3f GetAcceleration() const;
|
||||
[[nodiscard]] Common::Vec3f GetGyroscope() const;
|
||||
[[nodiscard]] Common::Vec3f GetRotations() const;
|
||||
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
|
||||
[[nodiscard]] Input::MotionStatus GetMotion() const;
|
||||
[[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
|
||||
int gyro_magnitude) const;
|
||||
|
||||
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
|
||||
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
|
||||
|
||||
private:
|
||||
void ResetOrientation();
|
||||
void SetOrientationFromAccelerometer();
|
||||
|
||||
// PID constants
|
||||
f32 kp;
|
||||
f32 ki;
|
||||
f32 kd;
|
||||
|
||||
// PID errors
|
||||
Common::Vec3f real_error;
|
||||
Common::Vec3f integral_error;
|
||||
Common::Vec3f derivative_error;
|
||||
|
||||
Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
|
||||
Common::Vec3f rotations;
|
||||
Common::Vec3f accel;
|
||||
Common::Vec3f gyro;
|
||||
Common::Vec3f gyro_drift;
|
||||
|
||||
f32 gyro_threshold = 0.0f;
|
||||
u32 reset_counter = 0;
|
||||
bool reset_enabled = true;
|
||||
bool only_accelerometer = true;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,223 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
|
||||
namespace MouseInput {
|
||||
|
||||
Mouse::Mouse() {
|
||||
update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
|
||||
}
|
||||
|
||||
Mouse::~Mouse() = default;
|
||||
|
||||
void Mouse::UpdateThread(std::stop_token stop_token) {
|
||||
constexpr int update_time = 10;
|
||||
while (!stop_token.stop_requested()) {
|
||||
for (MouseInfo& info : mouse_info) {
|
||||
const Common::Vec3f angular_direction{
|
||||
-info.tilt_direction.y,
|
||||
0.0f,
|
||||
-info.tilt_direction.x,
|
||||
};
|
||||
|
||||
info.motion.SetGyroscope(angular_direction * info.tilt_speed);
|
||||
info.motion.UpdateRotation(update_time * 1000);
|
||||
info.motion.UpdateOrientation(update_time * 1000);
|
||||
info.tilt_speed = 0;
|
||||
info.data.motion = info.motion.GetMotion();
|
||||
if (Settings::values.mouse_panning) {
|
||||
info.last_mouse_change *= 0.96f;
|
||||
info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
|
||||
static_cast<int>(16 * -info.last_mouse_change.y)};
|
||||
}
|
||||
}
|
||||
if (configuring) {
|
||||
UpdateYuzuSettings();
|
||||
}
|
||||
if (mouse_panning_timout++ > 20) {
|
||||
StopPanning();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::UpdateYuzuSettings() {
|
||||
if (buttons == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mouse_queue.Push(MouseStatus{
|
||||
.button = last_button,
|
||||
});
|
||||
}
|
||||
|
||||
void Mouse::PressButton(int x, int y, MouseButton button_) {
|
||||
const auto button_index = static_cast<std::size_t>(button_);
|
||||
if (button_index >= mouse_info.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto button = 1U << button_index;
|
||||
buttons |= static_cast<u16>(button);
|
||||
last_button = button_;
|
||||
|
||||
mouse_info[button_index].mouse_origin = Common::MakeVec(x, y);
|
||||
mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y);
|
||||
mouse_info[button_index].data.pressed = true;
|
||||
}
|
||||
|
||||
void Mouse::StopPanning() {
|
||||
for (MouseInfo& info : mouse_info) {
|
||||
if (Settings::values.mouse_panning) {
|
||||
info.data.axis = {};
|
||||
info.tilt_speed = 0;
|
||||
info.last_mouse_change = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
|
||||
for (MouseInfo& info : mouse_info) {
|
||||
if (Settings::values.mouse_panning) {
|
||||
auto mouse_change =
|
||||
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
|
||||
mouse_panning_timout = 0;
|
||||
|
||||
if (mouse_change.y == 0 && mouse_change.x == 0) {
|
||||
continue;
|
||||
}
|
||||
const auto mouse_change_length = mouse_change.Length();
|
||||
if (mouse_change_length < 3.0f) {
|
||||
mouse_change /= mouse_change_length / 3.0f;
|
||||
}
|
||||
|
||||
info.last_mouse_change = (info.last_mouse_change * 0.91f) + (mouse_change * 0.09f);
|
||||
|
||||
const auto last_mouse_change_length = info.last_mouse_change.Length();
|
||||
if (last_mouse_change_length > 8.0f) {
|
||||
info.last_mouse_change /= last_mouse_change_length / 8.0f;
|
||||
} else if (last_mouse_change_length < 1.0f) {
|
||||
info.last_mouse_change = mouse_change / mouse_change.Length();
|
||||
}
|
||||
|
||||
info.tilt_direction = info.last_mouse_change;
|
||||
info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.data.pressed) {
|
||||
const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin;
|
||||
const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position;
|
||||
info.last_mouse_position = Common::MakeVec(x, y);
|
||||
info.data.axis = {mouse_move.x, -mouse_move.y};
|
||||
|
||||
if (mouse_change.x == 0 && mouse_change.y == 0) {
|
||||
info.tilt_speed = 0;
|
||||
} else {
|
||||
info.tilt_direction = mouse_change.Cast<float>();
|
||||
info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::ReleaseButton(MouseButton button_) {
|
||||
const auto button_index = static_cast<std::size_t>(button_);
|
||||
if (button_index >= mouse_info.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto button = 1U << button_index;
|
||||
buttons &= static_cast<u16>(0xFF - button);
|
||||
|
||||
mouse_info[button_index].tilt_speed = 0;
|
||||
mouse_info[button_index].data.pressed = false;
|
||||
mouse_info[button_index].data.axis = {0, 0};
|
||||
}
|
||||
|
||||
void Mouse::ReleaseAllButtons() {
|
||||
buttons = 0;
|
||||
for (auto& info : mouse_info) {
|
||||
info.tilt_speed = 0;
|
||||
info.data.pressed = false;
|
||||
info.data.axis = {0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse::BeginConfiguration() {
|
||||
buttons = 0;
|
||||
last_button = MouseButton::Undefined;
|
||||
mouse_queue.Clear();
|
||||
configuring = true;
|
||||
}
|
||||
|
||||
void Mouse::EndConfiguration() {
|
||||
buttons = 0;
|
||||
for (MouseInfo& info : mouse_info) {
|
||||
info.tilt_speed = 0;
|
||||
info.data.pressed = false;
|
||||
info.data.axis = {0, 0};
|
||||
}
|
||||
last_button = MouseButton::Undefined;
|
||||
mouse_queue.Clear();
|
||||
configuring = false;
|
||||
}
|
||||
|
||||
bool Mouse::ToggleButton(std::size_t button_) {
|
||||
if (button_ >= mouse_info.size()) {
|
||||
return false;
|
||||
}
|
||||
const auto button = 1U << button_;
|
||||
const bool button_state = (toggle_buttons & button) != 0;
|
||||
const bool button_lock = (lock_buttons & button) != 0;
|
||||
|
||||
if (button_lock) {
|
||||
return button_state;
|
||||
}
|
||||
|
||||
lock_buttons |= static_cast<u16>(button);
|
||||
|
||||
if (button_state) {
|
||||
toggle_buttons &= static_cast<u16>(0xFF - button);
|
||||
} else {
|
||||
toggle_buttons |= static_cast<u16>(button);
|
||||
}
|
||||
|
||||
return !button_state;
|
||||
}
|
||||
|
||||
bool Mouse::UnlockButton(std::size_t button_) {
|
||||
if (button_ >= mouse_info.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto button = 1U << button_;
|
||||
const bool button_state = (toggle_buttons & button) != 0;
|
||||
|
||||
lock_buttons &= static_cast<u16>(0xFF - button);
|
||||
|
||||
return button_state;
|
||||
}
|
||||
|
||||
Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() {
|
||||
return mouse_queue;
|
||||
}
|
||||
|
||||
const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const {
|
||||
return mouse_queue;
|
||||
}
|
||||
|
||||
MouseData& Mouse::GetMouseState(std::size_t button) {
|
||||
return mouse_info[button].data;
|
||||
}
|
||||
|
||||
const MouseData& Mouse::GetMouseState(std::size_t button) const {
|
||||
return mouse_info[button].data;
|
||||
}
|
||||
} // namespace MouseInput
|
@@ -1,116 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/motion_input.h"
|
||||
|
||||
namespace MouseInput {
|
||||
|
||||
enum class MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Wheel,
|
||||
Backward,
|
||||
Forward,
|
||||
Task,
|
||||
Extra,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct MouseStatus {
|
||||
MouseButton button{MouseButton::Undefined};
|
||||
};
|
||||
|
||||
struct MouseData {
|
||||
bool pressed{};
|
||||
std::array<int, 2> axis{};
|
||||
Input::MotionStatus motion{};
|
||||
Input::TouchStatus touch{};
|
||||
};
|
||||
|
||||
class Mouse {
|
||||
public:
|
||||
Mouse();
|
||||
~Mouse();
|
||||
|
||||
/// Used for polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
/**
|
||||
* Signals that a button is pressed.
|
||||
* @param x the x-coordinate of the cursor
|
||||
* @param y the y-coordinate of the cursor
|
||||
* @param button_ the button pressed
|
||||
*/
|
||||
void PressButton(int x, int y, MouseButton button_);
|
||||
|
||||
/**
|
||||
* 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, int center_x, int center_y);
|
||||
|
||||
/**
|
||||
* Signals that a button is released.
|
||||
* @param button_ the button pressed
|
||||
*/
|
||||
void ReleaseButton(MouseButton button_);
|
||||
|
||||
/**
|
||||
* Signals that all buttons are released
|
||||
*/
|
||||
void ReleaseAllButtons();
|
||||
|
||||
[[nodiscard]] bool ToggleButton(std::size_t button_);
|
||||
[[nodiscard]] bool UnlockButton(std::size_t button_);
|
||||
|
||||
[[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue();
|
||||
[[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const;
|
||||
|
||||
[[nodiscard]] MouseData& GetMouseState(std::size_t button);
|
||||
[[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
|
||||
|
||||
private:
|
||||
void UpdateThread(std::stop_token stop_token);
|
||||
void UpdateYuzuSettings();
|
||||
void StopPanning();
|
||||
|
||||
struct MouseInfo {
|
||||
InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
|
||||
Common::Vec2<int> mouse_origin;
|
||||
Common::Vec2<int> last_mouse_position;
|
||||
Common::Vec2<float> last_mouse_change;
|
||||
bool is_tilting = false;
|
||||
float sensitivity{0.120f};
|
||||
|
||||
float tilt_speed = 0;
|
||||
Common::Vec2<float> tilt_direction;
|
||||
MouseData data;
|
||||
};
|
||||
|
||||
u16 buttons{};
|
||||
u16 toggle_buttons{};
|
||||
u16 lock_buttons{};
|
||||
std::jthread update_thread;
|
||||
MouseButton last_button{MouseButton::Undefined};
|
||||
std::array<MouseInfo, 7> mouse_info;
|
||||
Common::SPSCQueue<MouseStatus> mouse_queue;
|
||||
bool configuring{false};
|
||||
int mouse_panning_timout{};
|
||||
};
|
||||
} // namespace MouseInput
|
@@ -1,299 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
#include "input_common/mouse/mouse_poller.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class MouseButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit MouseButton(u32 button_, bool toggle_, MouseInput::Mouse* mouse_input_)
|
||||
: button(button_), toggle(toggle_), mouse_input(mouse_input_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
const bool button_state = mouse_input->GetMouseState(button).pressed;
|
||||
if (!toggle) {
|
||||
return button_state;
|
||||
}
|
||||
|
||||
if (button_state) {
|
||||
return mouse_input->ToggleButton(button);
|
||||
}
|
||||
return mouse_input->UnlockButton(button);
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 button;
|
||||
const bool toggle;
|
||||
MouseInput::Mouse* mouse_input;
|
||||
};
|
||||
|
||||
MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
|
||||
: mouse_input(std::move(mouse_input_)) {}
|
||||
|
||||
std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto button_id = params.Get("button", 0);
|
||||
const auto toggle = params.Get("toggle", false);
|
||||
|
||||
return std::make_unique<MouseButton>(button_id, toggle, mouse_input.get());
|
||||
}
|
||||
|
||||
Common::ParamPackage MouseButtonFactory::GetNextInput() const {
|
||||
MouseInput::MouseStatus pad;
|
||||
Common::ParamPackage params;
|
||||
auto& queue = mouse_input->GetMouseQueue();
|
||||
while (queue.Pop(pad)) {
|
||||
// This while loop will break on the earliest detected button
|
||||
if (pad.button != MouseInput::MouseButton::Undefined) {
|
||||
params.Set("engine", "mouse");
|
||||
params.Set("button", static_cast<u16>(pad.button));
|
||||
params.Set("toggle", false);
|
||||
return params;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
void MouseButtonFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
mouse_input->BeginConfiguration();
|
||||
}
|
||||
|
||||
void MouseButtonFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
mouse_input->EndConfiguration();
|
||||
}
|
||||
|
||||
class MouseAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
|
||||
float deadzone_, float range_, const MouseInput::Mouse* mouse_input_)
|
||||
: button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
|
||||
deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {}
|
||||
|
||||
float GetAxis(u32 axis) const {
|
||||
std::lock_guard lock{mutex};
|
||||
const auto axis_value =
|
||||
static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
|
||||
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.10f;
|
||||
return axis_value * sensitivity / (100.0f * range);
|
||||
}
|
||||
|
||||
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);
|
||||
if (invert_x) {
|
||||
x = -x;
|
||||
}
|
||||
if (invert_y) {
|
||||
y = -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};
|
||||
}
|
||||
|
||||
std::tuple<float, float> GetRawStatus() const override {
|
||||
const float x = GetAxis(axis_x);
|
||||
const float y = GetAxis(axis_y);
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
Input::AnalogProperties GetAnalogProperties() const override {
|
||||
return {deadzone, range, 0.5f};
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 button;
|
||||
const u32 axis_x;
|
||||
const u32 axis_y;
|
||||
const bool invert_x;
|
||||
const bool invert_y;
|
||||
const float deadzone;
|
||||
const float range;
|
||||
const MouseInput::Mouse* mouse_input;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from GC Adapter
|
||||
MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
|
||||
: mouse_input(std::move(mouse_input_)) {}
|
||||
|
||||
/**
|
||||
* 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> MouseAnalogFactory::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);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
|
||||
const std::string invert_x_value = params.Get("invert_x", "+");
|
||||
const std::string invert_y_value = params.Get("invert_y", "+");
|
||||
const bool invert_x = invert_x_value == "-";
|
||||
const bool invert_y = invert_y_value == "-";
|
||||
|
||||
return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
|
||||
mouse_input.get());
|
||||
}
|
||||
|
||||
void MouseAnalogFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
mouse_input->BeginConfiguration();
|
||||
}
|
||||
|
||||
void MouseAnalogFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
mouse_input->EndConfiguration();
|
||||
}
|
||||
|
||||
Common::ParamPackage MouseAnalogFactory::GetNextInput() const {
|
||||
MouseInput::MouseStatus pad;
|
||||
Common::ParamPackage params;
|
||||
auto& queue = mouse_input->GetMouseQueue();
|
||||
while (queue.Pop(pad)) {
|
||||
// This while loop will break on the earliest detected button
|
||||
if (pad.button != MouseInput::MouseButton::Undefined) {
|
||||
params.Set("engine", "mouse");
|
||||
params.Set("port", static_cast<u16>(pad.button));
|
||||
params.Set("axis_x", 0);
|
||||
params.Set("axis_y", 1);
|
||||
params.Set("invert_x", "+");
|
||||
params.Set("invert_y", "+");
|
||||
return params;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
class MouseMotion final : public Input::MotionDevice {
|
||||
public:
|
||||
explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_)
|
||||
: button(button_), mouse_input(mouse_input_) {}
|
||||
|
||||
Input::MotionStatus GetStatus() const override {
|
||||
return mouse_input->GetMouseState(button).motion;
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 button;
|
||||
const MouseInput::Mouse* mouse_input;
|
||||
};
|
||||
|
||||
MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
|
||||
: mouse_input(std::move(mouse_input_)) {}
|
||||
|
||||
std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto button_id = params.Get("button", 0);
|
||||
|
||||
return std::make_unique<MouseMotion>(button_id, mouse_input.get());
|
||||
}
|
||||
|
||||
Common::ParamPackage MouseMotionFactory::GetNextInput() const {
|
||||
MouseInput::MouseStatus pad;
|
||||
Common::ParamPackage params;
|
||||
auto& queue = mouse_input->GetMouseQueue();
|
||||
while (queue.Pop(pad)) {
|
||||
// This while loop will break on the earliest detected button
|
||||
if (pad.button != MouseInput::MouseButton::Undefined) {
|
||||
params.Set("engine", "mouse");
|
||||
params.Set("button", static_cast<u16>(pad.button));
|
||||
return params;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
void MouseMotionFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
mouse_input->BeginConfiguration();
|
||||
}
|
||||
|
||||
void MouseMotionFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
mouse_input->EndConfiguration();
|
||||
}
|
||||
|
||||
class MouseTouch final : public Input::TouchDevice {
|
||||
public:
|
||||
explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_)
|
||||
: button(button_), mouse_input(mouse_input_) {}
|
||||
|
||||
Input::TouchStatus GetStatus() const override {
|
||||
return mouse_input->GetMouseState(button).touch;
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 button;
|
||||
const MouseInput::Mouse* mouse_input;
|
||||
};
|
||||
|
||||
MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
|
||||
: mouse_input(std::move(mouse_input_)) {}
|
||||
|
||||
std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) {
|
||||
const auto button_id = params.Get("button", 0);
|
||||
|
||||
return std::make_unique<MouseTouch>(button_id, mouse_input.get());
|
||||
}
|
||||
|
||||
Common::ParamPackage MouseTouchFactory::GetNextInput() const {
|
||||
MouseInput::MouseStatus pad;
|
||||
Common::ParamPackage params;
|
||||
auto& queue = mouse_input->GetMouseQueue();
|
||||
while (queue.Pop(pad)) {
|
||||
// This while loop will break on the earliest detected button
|
||||
if (pad.button != MouseInput::MouseButton::Undefined) {
|
||||
params.Set("engine", "mouse");
|
||||
params.Set("button", static_cast<u16>(pad.button));
|
||||
return params;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
void MouseTouchFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
mouse_input->BeginConfiguration();
|
||||
}
|
||||
|
||||
void MouseTouchFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
mouse_input->EndConfiguration();
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,109 +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 "core/frontend/input.h"
|
||||
#include "input_common/mouse/mouse_input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* A button device factory representing a mouse. It receives mouse events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> {
|
||||
public:
|
||||
explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
|
||||
|
||||
/**
|
||||
* Creates a button device from a button press
|
||||
* @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;
|
||||
|
||||
Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MouseInput::Mouse> mouse_input;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from mouse
|
||||
class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
|
||||
public:
|
||||
explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
|
||||
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MouseInput::Mouse> mouse_input;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
/// A motion device factory that creates motion devices from mouse
|
||||
class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
||||
public:
|
||||
explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
|
||||
|
||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MouseInput::Mouse> mouse_input;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
/// An touch device factory that creates touch devices from mouse
|
||||
class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> {
|
||||
public:
|
||||
explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
|
||||
|
||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::ParamPackage GetNextInput() const;
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<MouseInput::Mouse> mouse_input;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
} // 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,51 +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 "common/param_package.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
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) {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual std::vector<Common::ParamPackage> GetInputDevices() {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
|
||||
return {};
|
||||
}
|
||||
virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
|
||||
return {};
|
||||
}
|
||||
virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class NullState : public State {
|
||||
public:
|
||||
};
|
||||
|
||||
std::unique_ptr<State> Init();
|
||||
|
||||
} // namespace InputCommon::SDL
|
File diff suppressed because it is too large
Load Diff
@@ -1,114 +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 <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
|
||||
union SDL_Event;
|
||||
using SDL_GameController = struct _SDL_GameController;
|
||||
using SDL_Joystick = struct _SDL_Joystick;
|
||||
using SDL_JoystickID = s32;
|
||||
|
||||
using ButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
|
||||
using ZButtonBindings =
|
||||
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
|
||||
|
||||
namespace InputCommon::SDL {
|
||||
|
||||
class SDLAnalogFactory;
|
||||
class SDLButtonFactory;
|
||||
class SDLMotionFactory;
|
||||
class SDLVibrationFactory;
|
||||
class SDLJoystick;
|
||||
|
||||
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);
|
||||
|
||||
/// 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 std::string& guid, int port);
|
||||
|
||||
/// 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;
|
||||
|
||||
std::vector<Common::ParamPackage> GetInputDevices() override;
|
||||
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
MotionMapping GetMotionMappingForDevice(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();
|
||||
|
||||
/// 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;
|
||||
|
||||
// Set to true if SDL supports game controller subsystem
|
||||
bool has_gamecontroller = false;
|
||||
|
||||
/// 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;
|
||||
|
||||
std::shared_ptr<SDLButtonFactory> button_factory;
|
||||
std::shared_ptr<SDLAnalogFactory> analog_factory;
|
||||
std::shared_ptr<SDLVibrationFactory> vibration_factory;
|
||||
std::shared_ptr<SDLMotionFactory> motion_factory;
|
||||
|
||||
bool start_thread = false;
|
||||
std::atomic<bool> initialized = false;
|
||||
|
||||
std::thread poll_thread;
|
||||
};
|
||||
} // namespace InputCommon::SDL
|
@@ -1,47 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "input_common/settings.h"
|
||||
|
||||
namespace Settings {
|
||||
namespace NativeButton {
|
||||
const std::array<const char*, NumButtons> mapping = {{
|
||||
"button_a", "button_b", "button_x", "button_y", "button_lstick",
|
||||
"button_rstick", "button_l", "button_r", "button_zl", "button_zr",
|
||||
"button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
|
||||
"button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot",
|
||||
}};
|
||||
}
|
||||
|
||||
namespace NativeAnalog {
|
||||
const std::array<const char*, NumAnalogs> mapping = {{
|
||||
"lstick",
|
||||
"rstick",
|
||||
}};
|
||||
}
|
||||
|
||||
namespace NativeVibration {
|
||||
const std::array<const char*, NumVibrations> mapping = {{
|
||||
"left_vibration_device",
|
||||
"right_vibration_device",
|
||||
}};
|
||||
}
|
||||
|
||||
namespace NativeMotion {
|
||||
const std::array<const char*, NumMotions> mapping = {{
|
||||
"motionleft",
|
||||
"motionright",
|
||||
}};
|
||||
}
|
||||
|
||||
namespace NativeMouseButton {
|
||||
const std::array<const char*, NumMouseButtons> mapping = {{
|
||||
"left",
|
||||
"right",
|
||||
"middle",
|
||||
"forward",
|
||||
"back",
|
||||
}};
|
||||
}
|
||||
} // namespace Settings
|
@@ -1,372 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Settings {
|
||||
namespace NativeButton {
|
||||
enum Values : int {
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LStick,
|
||||
RStick,
|
||||
L,
|
||||
R,
|
||||
ZL,
|
||||
ZR,
|
||||
Plus,
|
||||
Minus,
|
||||
|
||||
DLeft,
|
||||
DUp,
|
||||
DRight,
|
||||
DDown,
|
||||
|
||||
SL,
|
||||
SR,
|
||||
|
||||
Home,
|
||||
Screenshot,
|
||||
|
||||
NumButtons,
|
||||
};
|
||||
|
||||
constexpr int BUTTON_HID_BEGIN = A;
|
||||
constexpr int BUTTON_NS_BEGIN = Home;
|
||||
|
||||
constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
|
||||
constexpr int BUTTON_NS_END = NumButtons;
|
||||
|
||||
constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
|
||||
constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
|
||||
|
||||
extern const std::array<const char*, NumButtons> mapping;
|
||||
|
||||
} // namespace NativeButton
|
||||
|
||||
namespace NativeAnalog {
|
||||
enum Values : int {
|
||||
LStick,
|
||||
RStick,
|
||||
|
||||
NumAnalogs,
|
||||
};
|
||||
|
||||
constexpr int STICK_HID_BEGIN = LStick;
|
||||
constexpr int STICK_HID_END = NumAnalogs;
|
||||
constexpr int NUM_STICKS_HID = NumAnalogs;
|
||||
|
||||
extern const std::array<const char*, NumAnalogs> mapping;
|
||||
} // namespace NativeAnalog
|
||||
|
||||
namespace NativeVibration {
|
||||
enum Values : int {
|
||||
LeftVibrationDevice,
|
||||
RightVibrationDevice,
|
||||
|
||||
NumVibrations,
|
||||
};
|
||||
|
||||
constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
|
||||
constexpr int VIBRATION_HID_END = NumVibrations;
|
||||
constexpr int NUM_VIBRATIONS_HID = NumVibrations;
|
||||
|
||||
extern const std::array<const char*, NumVibrations> mapping;
|
||||
}; // namespace NativeVibration
|
||||
|
||||
namespace NativeMotion {
|
||||
enum Values : int {
|
||||
MotionLeft,
|
||||
MotionRight,
|
||||
|
||||
NumMotions,
|
||||
};
|
||||
|
||||
constexpr int MOTION_HID_BEGIN = MotionLeft;
|
||||
constexpr int MOTION_HID_END = NumMotions;
|
||||
constexpr int NUM_MOTIONS_HID = NumMotions;
|
||||
|
||||
extern const std::array<const char*, NumMotions> mapping;
|
||||
} // namespace NativeMotion
|
||||
|
||||
namespace NativeMouseButton {
|
||||
enum Values {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Forward,
|
||||
Back,
|
||||
|
||||
NumMouseButtons,
|
||||
};
|
||||
|
||||
constexpr int MOUSE_HID_BEGIN = Left;
|
||||
constexpr int MOUSE_HID_END = NumMouseButtons;
|
||||
constexpr int NUM_MOUSE_HID = NumMouseButtons;
|
||||
|
||||
extern const std::array<const char*, NumMouseButtons> mapping;
|
||||
} // namespace NativeMouseButton
|
||||
|
||||
namespace NativeKeyboard {
|
||||
enum Keys {
|
||||
None,
|
||||
Error,
|
||||
|
||||
A = 4,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
N1,
|
||||
N2,
|
||||
N3,
|
||||
N4,
|
||||
N5,
|
||||
N6,
|
||||
N7,
|
||||
N8,
|
||||
N9,
|
||||
N0,
|
||||
Enter,
|
||||
Escape,
|
||||
Backspace,
|
||||
Tab,
|
||||
Space,
|
||||
Minus,
|
||||
Equal,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
Backslash,
|
||||
Tilde,
|
||||
Semicolon,
|
||||
Apostrophe,
|
||||
Grave,
|
||||
Comma,
|
||||
Dot,
|
||||
Slash,
|
||||
CapsLockKey,
|
||||
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
|
||||
SystemRequest,
|
||||
ScrollLockKey,
|
||||
Pause,
|
||||
Insert,
|
||||
Home,
|
||||
PageUp,
|
||||
Delete,
|
||||
End,
|
||||
PageDown,
|
||||
Right,
|
||||
Left,
|
||||
Down,
|
||||
Up,
|
||||
|
||||
NumLockKey,
|
||||
KPSlash,
|
||||
KPAsterisk,
|
||||
KPMinus,
|
||||
KPPlus,
|
||||
KPEnter,
|
||||
KP1,
|
||||
KP2,
|
||||
KP3,
|
||||
KP4,
|
||||
KP5,
|
||||
KP6,
|
||||
KP7,
|
||||
KP8,
|
||||
KP9,
|
||||
KP0,
|
||||
KPDot,
|
||||
|
||||
Key102,
|
||||
Compose,
|
||||
Power,
|
||||
KPEqual,
|
||||
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
|
||||
Open,
|
||||
Help,
|
||||
Properties,
|
||||
Front,
|
||||
Stop,
|
||||
Repeat,
|
||||
Undo,
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
Find,
|
||||
Mute,
|
||||
VolumeUp,
|
||||
VolumeDown,
|
||||
CapsLockActive,
|
||||
NumLockActive,
|
||||
ScrollLockActive,
|
||||
KPComma,
|
||||
|
||||
KPLeftParenthesis,
|
||||
KPRightParenthesis,
|
||||
|
||||
LeftControlKey = 0xE0,
|
||||
LeftShiftKey,
|
||||
LeftAltKey,
|
||||
LeftMetaKey,
|
||||
RightControlKey,
|
||||
RightShiftKey,
|
||||
RightAltKey,
|
||||
RightMetaKey,
|
||||
|
||||
MediaPlayPause,
|
||||
MediaStopCD,
|
||||
MediaPrevious,
|
||||
MediaNext,
|
||||
MediaEject,
|
||||
MediaVolumeUp,
|
||||
MediaVolumeDown,
|
||||
MediaMute,
|
||||
MediaWebsite,
|
||||
MediaBack,
|
||||
MediaForward,
|
||||
MediaStop,
|
||||
MediaFind,
|
||||
MediaScrollUp,
|
||||
MediaScrollDown,
|
||||
MediaEdit,
|
||||
MediaSleep,
|
||||
MediaCoffee,
|
||||
MediaRefresh,
|
||||
MediaCalculator,
|
||||
|
||||
NumKeyboardKeys,
|
||||
};
|
||||
|
||||
static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
|
||||
|
||||
enum Modifiers {
|
||||
LeftControl,
|
||||
LeftShift,
|
||||
LeftAlt,
|
||||
LeftMeta,
|
||||
RightControl,
|
||||
RightShift,
|
||||
RightAlt,
|
||||
RightMeta,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
NumLock,
|
||||
|
||||
NumKeyboardMods,
|
||||
};
|
||||
|
||||
constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
|
||||
constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
|
||||
constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
|
||||
|
||||
constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
|
||||
constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
|
||||
constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
|
||||
|
||||
} // namespace NativeKeyboard
|
||||
|
||||
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
|
||||
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
|
||||
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
|
||||
using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
|
||||
|
||||
using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
|
||||
using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
|
||||
using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
|
||||
|
||||
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
||||
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
|
||||
|
||||
enum class ControllerType {
|
||||
ProController,
|
||||
DualJoyconDetached,
|
||||
LeftJoycon,
|
||||
RightJoycon,
|
||||
Handheld,
|
||||
GameCube,
|
||||
};
|
||||
|
||||
struct PlayerInput {
|
||||
bool connected;
|
||||
ControllerType controller_type;
|
||||
ButtonsRaw buttons;
|
||||
AnalogsRaw analogs;
|
||||
VibrationsRaw vibrations;
|
||||
MotionsRaw motions;
|
||||
|
||||
bool vibration_enabled;
|
||||
int vibration_strength;
|
||||
|
||||
u32 body_color_left;
|
||||
u32 body_color_right;
|
||||
u32 button_color_left;
|
||||
u32 button_color_right;
|
||||
};
|
||||
|
||||
struct TouchscreenInput {
|
||||
bool enabled;
|
||||
std::string device;
|
||||
|
||||
u32 finger;
|
||||
u32 diameter_x;
|
||||
u32 diameter_y;
|
||||
u32 rotation_angle;
|
||||
};
|
||||
} // namespace Settings
|
@@ -1,455 +0,0 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/tas/tas_input.h"
|
||||
|
||||
namespace TasInput {
|
||||
|
||||
// Supported keywords and buttons from a TAS file
|
||||
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
|
||||
std::pair{"KEY_A", TasButton::BUTTON_A},
|
||||
{"KEY_B", TasButton::BUTTON_B},
|
||||
{"KEY_X", TasButton::BUTTON_X},
|
||||
{"KEY_Y", TasButton::BUTTON_Y},
|
||||
{"KEY_LSTICK", TasButton::STICK_L},
|
||||
{"KEY_RSTICK", TasButton::STICK_R},
|
||||
{"KEY_L", TasButton::TRIGGER_L},
|
||||
{"KEY_R", TasButton::TRIGGER_R},
|
||||
{"KEY_PLUS", TasButton::BUTTON_PLUS},
|
||||
{"KEY_MINUS", TasButton::BUTTON_MINUS},
|
||||
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
|
||||
{"KEY_DUP", TasButton::BUTTON_UP},
|
||||
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
|
||||
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
|
||||
{"KEY_SL", TasButton::BUTTON_SL},
|
||||
{"KEY_SR", TasButton::BUTTON_SR},
|
||||
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
|
||||
{"KEY_HOME", TasButton::BUTTON_HOME},
|
||||
{"KEY_ZL", TasButton::TRIGGER_ZL},
|
||||
{"KEY_ZR", TasButton::TRIGGER_ZR},
|
||||
};
|
||||
|
||||
Tas::Tas() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
needs_reset = true;
|
||||
return;
|
||||
}
|
||||
LoadTasFiles();
|
||||
}
|
||||
|
||||
Tas::~Tas() {
|
||||
Stop();
|
||||
};
|
||||
|
||||
void Tas::LoadTasFiles() {
|
||||
script_length = 0;
|
||||
for (size_t i = 0; i < commands.size(); i++) {
|
||||
LoadTasFile(i);
|
||||
if (commands[i].size() > script_length) {
|
||||
script_length = commands[i].size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::LoadTasFile(size_t player_index) {
|
||||
if (!commands[player_index].empty()) {
|
||||
commands[player_index].clear();
|
||||
}
|
||||
std::string file =
|
||||
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
|
||||
fmt::format("script0-{}.txt", player_index + 1),
|
||||
Common::FS::FileType::BinaryFile);
|
||||
std::stringstream command_line(file);
|
||||
std::string line;
|
||||
int frame_no = 0;
|
||||
while (std::getline(command_line, line, '\n')) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
LOG_DEBUG(Input, "Loading line: {}", line);
|
||||
std::smatch m;
|
||||
|
||||
std::stringstream linestream(line);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
while (std::getline(linestream, segment, ' ')) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
|
||||
if (seglist.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (frame_no < std::stoi(seglist.at(0))) {
|
||||
commands[player_index].push_back({});
|
||||
frame_no++;
|
||||
}
|
||||
|
||||
TASCommand command = {
|
||||
.buttons = ReadCommandButtons(seglist.at(1)),
|
||||
.l_axis = ReadCommandAxis(seglist.at(2)),
|
||||
.r_axis = ReadCommandAxis(seglist.at(3)),
|
||||
};
|
||||
commands[player_index].push_back(command);
|
||||
frame_no++;
|
||||
}
|
||||
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
|
||||
}
|
||||
|
||||
void Tas::WriteTasFile(std::u8string file_name) {
|
||||
std::string output_text;
|
||||
for (size_t frame = 0; frame < record_commands.size(); frame++) {
|
||||
if (!output_text.empty()) {
|
||||
output_text += "\n";
|
||||
}
|
||||
const TASCommand& line = record_commands[frame];
|
||||
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
|
||||
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
|
||||
}
|
||||
const auto bytes_written = Common::FS::WriteStringToFile(
|
||||
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
|
||||
Common::FS::FileType::TextFile, output_text);
|
||||
if (bytes_written == output_text.size()) {
|
||||
LOG_INFO(Input, "TAS file written to file!");
|
||||
} else {
|
||||
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
|
||||
output_text.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
|
||||
auto [x, y] = old;
|
||||
return {x, -y};
|
||||
}
|
||||
|
||||
void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
|
||||
last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
|
||||
}
|
||||
|
||||
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
|
||||
TasState state;
|
||||
if (is_recording) {
|
||||
return {TasState::Recording, 0, record_commands.size()};
|
||||
}
|
||||
|
||||
if (is_running) {
|
||||
state = TasState::Running;
|
||||
} else {
|
||||
state = TasState::Stopped;
|
||||
}
|
||||
|
||||
return {state, current_command, script_length};
|
||||
}
|
||||
|
||||
std::string Tas::DebugButtons(u32 buttons) const {
|
||||
return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
|
||||
}
|
||||
|
||||
std::string Tas::DebugJoystick(float x, float y) const {
|
||||
return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
|
||||
}
|
||||
|
||||
std::string Tas::DebugInput(const TasData& data) const {
|
||||
return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
|
||||
DebugJoystick(data.axis[0], data.axis[1]),
|
||||
DebugJoystick(data.axis[2], data.axis[3]));
|
||||
}
|
||||
|
||||
std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const {
|
||||
std::string returns = "[ ";
|
||||
for (size_t i = 0; i < arr.size(); i++) {
|
||||
returns += DebugInput(arr[i]);
|
||||
if (i != arr.size() - 1) {
|
||||
returns += " , ";
|
||||
}
|
||||
}
|
||||
return returns + "]";
|
||||
}
|
||||
|
||||
std::string Tas::ButtonsToString(u32 button) const {
|
||||
std::string returns;
|
||||
for (auto [text_button, tas_button] : text_to_tas_button) {
|
||||
if ((button & static_cast<u32>(tas_button)) != 0)
|
||||
returns += fmt::format(", {}", text_button.substr(4));
|
||||
}
|
||||
return returns.empty() ? "" : returns.substr(2);
|
||||
}
|
||||
|
||||
void Tas::UpdateThread() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
if (is_running) {
|
||||
Stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_recording) {
|
||||
record_commands.push_back(last_input);
|
||||
}
|
||||
if (needs_reset) {
|
||||
current_command = 0;
|
||||
needs_reset = false;
|
||||
LoadTasFiles();
|
||||
LOG_DEBUG(Input, "tas_reset done");
|
||||
}
|
||||
|
||||
if (!is_running) {
|
||||
tas_data.fill({});
|
||||
return;
|
||||
}
|
||||
if (current_command < script_length) {
|
||||
LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
|
||||
size_t frame = current_command++;
|
||||
for (size_t i = 0; i < commands.size(); i++) {
|
||||
if (frame < commands[i].size()) {
|
||||
TASCommand command = commands[i][frame];
|
||||
tas_data[i].buttons = command.buttons;
|
||||
auto [l_axis_x, l_axis_y] = command.l_axis;
|
||||
tas_data[i].axis[0] = l_axis_x;
|
||||
tas_data[i].axis[1] = l_axis_y;
|
||||
auto [r_axis_x, r_axis_y] = command.r_axis;
|
||||
tas_data[i].axis[2] = r_axis_x;
|
||||
tas_data[i].axis[3] = r_axis_y;
|
||||
} else {
|
||||
tas_data[i] = {};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_running = Settings::values.tas_loop.GetValue();
|
||||
current_command = 0;
|
||||
tas_data.fill({});
|
||||
if (!is_running) {
|
||||
SwapToStoredController();
|
||||
}
|
||||
}
|
||||
LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
|
||||
}
|
||||
|
||||
TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
|
||||
std::stringstream linestream(line);
|
||||
std::string segment;
|
||||
std::vector<std::string> seglist;
|
||||
|
||||
while (std::getline(linestream, segment, ';')) {
|
||||
seglist.push_back(segment);
|
||||
}
|
||||
|
||||
const float x = std::stof(seglist.at(0)) / 32767.0f;
|
||||
const float y = std::stof(seglist.at(1)) / 32767.0f;
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
u32 Tas::ReadCommandButtons(const std::string& data) const {
|
||||
std::stringstream button_text(data);
|
||||
std::string line;
|
||||
u32 buttons = 0;
|
||||
while (std::getline(button_text, line, ';')) {
|
||||
for (auto [text, tas_button] : text_to_tas_button) {
|
||||
if (text == line) {
|
||||
buttons |= static_cast<u32>(tas_button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
std::string Tas::WriteCommandAxis(TasAnalog data) const {
|
||||
auto [x, y] = data;
|
||||
std::string line;
|
||||
line += std::to_string(static_cast<int>(x * 32767));
|
||||
line += ";";
|
||||
line += std::to_string(static_cast<int>(y * 32767));
|
||||
return line;
|
||||
}
|
||||
|
||||
std::string Tas::WriteCommandButtons(u32 data) const {
|
||||
if (data == 0) {
|
||||
return "NONE";
|
||||
}
|
||||
|
||||
std::string line;
|
||||
u32 index = 0;
|
||||
while (data > 0) {
|
||||
if ((data & 1) == 1) {
|
||||
for (auto [text, tas_button] : text_to_tas_button) {
|
||||
if (tas_button == static_cast<TasButton>(1 << index)) {
|
||||
if (line.size() > 0) {
|
||||
line += ";";
|
||||
}
|
||||
line += text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
data >>= 1;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
void Tas::StartStop() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return;
|
||||
}
|
||||
if (is_running) {
|
||||
Stop();
|
||||
} else {
|
||||
is_running = true;
|
||||
SwapToTasController();
|
||||
}
|
||||
}
|
||||
|
||||
void Tas::Stop() {
|
||||
is_running = false;
|
||||
SwapToStoredController();
|
||||
}
|
||||
|
||||
void Tas::SwapToTasController() {
|
||||
if (!Settings::values.tas_swap_controllers) {
|
||||
return;
|
||||
}
|
||||
auto& players = Settings::values.players.GetValue();
|
||||
for (std::size_t index = 0; index < players.size(); index++) {
|
||||
auto& player = players[index];
|
||||
player_mappings[index] = player;
|
||||
|
||||
// Only swap active controllers
|
||||
if (!player.connected) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Common::ParamPackage tas_param;
|
||||
tas_param.Set("pad", static_cast<u8>(index));
|
||||
auto button_mapping = GetButtonMappingForDevice(tas_param);
|
||||
auto analog_mapping = GetAnalogMappingForDevice(tas_param);
|
||||
auto& buttons = player.buttons;
|
||||
auto& analogs = player.analogs;
|
||||
|
||||
for (std::size_t i = 0; i < buttons.size(); ++i) {
|
||||
buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
|
||||
}
|
||||
for (std::size_t i = 0; i < analogs.size(); ++i) {
|
||||
analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
|
||||
}
|
||||
}
|
||||
is_old_input_saved = true;
|
||||
Settings::values.is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
void Tas::SwapToStoredController() {
|
||||
if (!is_old_input_saved) {
|
||||
return;
|
||||
}
|
||||
auto& players = Settings::values.players.GetValue();
|
||||
for (std::size_t index = 0; index < players.size(); index++) {
|
||||
players[index] = player_mappings[index];
|
||||
}
|
||||
is_old_input_saved = false;
|
||||
Settings::values.is_device_reload_pending.store(true);
|
||||
}
|
||||
|
||||
void Tas::Reset() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return;
|
||||
}
|
||||
needs_reset = true;
|
||||
}
|
||||
|
||||
bool Tas::Record() {
|
||||
if (!Settings::values.tas_enable) {
|
||||
return true;
|
||||
}
|
||||
is_recording = !is_recording;
|
||||
return is_recording;
|
||||
}
|
||||
|
||||
void Tas::SaveRecording(bool overwrite_file) {
|
||||
if (is_recording) {
|
||||
return;
|
||||
}
|
||||
if (record_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
WriteTasFile(u8"record.txt");
|
||||
if (overwrite_file) {
|
||||
WriteTasFile(u8"script0-1.txt");
|
||||
}
|
||||
needs_reset = true;
|
||||
record_commands.clear();
|
||||
}
|
||||
|
||||
InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
// 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, TasButton>, 20>
|
||||
switch_to_tas_button = {
|
||||
std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
|
||||
{Settings::NativeButton::B, TasButton::BUTTON_B},
|
||||
{Settings::NativeButton::X, TasButton::BUTTON_X},
|
||||
{Settings::NativeButton::Y, TasButton::BUTTON_Y},
|
||||
{Settings::NativeButton::LStick, TasButton::STICK_L},
|
||||
{Settings::NativeButton::RStick, TasButton::STICK_R},
|
||||
{Settings::NativeButton::L, TasButton::TRIGGER_L},
|
||||
{Settings::NativeButton::R, TasButton::TRIGGER_R},
|
||||
{Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
|
||||
{Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
|
||||
{Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
|
||||
{Settings::NativeButton::DUp, TasButton::BUTTON_UP},
|
||||
{Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
|
||||
{Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
|
||||
{Settings::NativeButton::SL, TasButton::BUTTON_SL},
|
||||
{Settings::NativeButton::SR, TasButton::BUTTON_SR},
|
||||
{Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
|
||||
{Settings::NativeButton::Home, TasButton::BUTTON_HOME},
|
||||
{Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
|
||||
{Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
|
||||
};
|
||||
|
||||
InputCommon::ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
|
||||
Common::ParamPackage button_params({{"engine", "tas"}});
|
||||
button_params.Set("pad", params.Get("pad", 0));
|
||||
button_params.Set("button", static_cast<int>(tas_button));
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
|
||||
const Common::ParamPackage& params) const {
|
||||
|
||||
InputCommon::AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params;
|
||||
left_analog_params.Set("engine", "tas");
|
||||
left_analog_params.Set("pad", params.Get("pad", 0));
|
||||
left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
|
||||
left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||
Common::ParamPackage right_analog_params;
|
||||
right_analog_params.Set("engine", "tas");
|
||||
right_analog_params.Set("pad", params.Get("pad", 0));
|
||||
right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
|
||||
right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
const TasData& Tas::GetTasState(std::size_t pad) const {
|
||||
return tas_data[pad];
|
||||
}
|
||||
} // namespace TasInput
|
@@ -1,237 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_input.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
/*
|
||||
To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
|
||||
Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
|
||||
for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
|
||||
|
||||
A script file has the same format as TAS-nx uses, so final files will look like this:
|
||||
|
||||
1 KEY_B 0;0 0;0
|
||||
6 KEY_ZL 0;0 0;0
|
||||
41 KEY_ZL;KEY_Y 0;0 0;0
|
||||
43 KEY_X;KEY_A 32767;0 0;0
|
||||
44 KEY_A 32767;0 0;0
|
||||
45 KEY_A 32767;0 0;0
|
||||
46 KEY_A 32767;0 0;0
|
||||
47 KEY_A 32767;0 0;0
|
||||
|
||||
After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
|
||||
CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
|
||||
has. Playback can be started or stopped using CTRL+F5.
|
||||
|
||||
However, for playback to actually work, the correct input device has to be selected: In the Controls
|
||||
menu, select TAS from the device list for the controller that the script should be played on.
|
||||
|
||||
Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
|
||||
connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
|
||||
again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
|
||||
record.txt.
|
||||
|
||||
For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
|
||||
P1).
|
||||
*/
|
||||
|
||||
namespace TasInput {
|
||||
|
||||
constexpr size_t PLAYER_NUMBER = 8;
|
||||
|
||||
using TasAnalog = std::pair<float, float>;
|
||||
|
||||
enum class TasState {
|
||||
Running,
|
||||
Recording,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
enum class TasButton : u32 {
|
||||
BUTTON_A = 1U << 0,
|
||||
BUTTON_B = 1U << 1,
|
||||
BUTTON_X = 1U << 2,
|
||||
BUTTON_Y = 1U << 3,
|
||||
STICK_L = 1U << 4,
|
||||
STICK_R = 1U << 5,
|
||||
TRIGGER_L = 1U << 6,
|
||||
TRIGGER_R = 1U << 7,
|
||||
TRIGGER_ZL = 1U << 8,
|
||||
TRIGGER_ZR = 1U << 9,
|
||||
BUTTON_PLUS = 1U << 10,
|
||||
BUTTON_MINUS = 1U << 11,
|
||||
BUTTON_LEFT = 1U << 12,
|
||||
BUTTON_UP = 1U << 13,
|
||||
BUTTON_RIGHT = 1U << 14,
|
||||
BUTTON_DOWN = 1U << 15,
|
||||
BUTTON_SL = 1U << 16,
|
||||
BUTTON_SR = 1U << 17,
|
||||
BUTTON_HOME = 1U << 18,
|
||||
BUTTON_CAPTURE = 1U << 19,
|
||||
};
|
||||
|
||||
enum class TasAxes : u8 {
|
||||
StickX,
|
||||
StickY,
|
||||
SubstickX,
|
||||
SubstickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct TasData {
|
||||
u32 buttons{};
|
||||
std::array<float, 4> axis{};
|
||||
};
|
||||
|
||||
class Tas {
|
||||
public:
|
||||
Tas();
|
||||
~Tas();
|
||||
|
||||
// Changes the input status that will be stored in each frame
|
||||
void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
|
||||
|
||||
// Main loop that records or executes input
|
||||
void UpdateThread();
|
||||
|
||||
// Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
|
||||
void StartStop();
|
||||
|
||||
// Stop the TAS and reverts any controller profile
|
||||
void Stop();
|
||||
|
||||
// Sets the flag to reload the file and start from the begining in the next update
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* Sets the flag to enable or disable recording of inputs
|
||||
* @return Returns true if the current recording status is enabled
|
||||
*/
|
||||
bool Record();
|
||||
|
||||
// Saves contents of record_commands on a file if overwrite is enabled player 1 will be
|
||||
// overwritten with the recorded commands
|
||||
void SaveRecording(bool overwrite_file);
|
||||
|
||||
/**
|
||||
* Returns the current status values of TAS playback/recording
|
||||
* @return Tuple of
|
||||
* TasState indicating the current state out of Running, Recording or Stopped ;
|
||||
* Current playback progress or amount of frames (so far) for Recording ;
|
||||
* Total length of script file currently loaded or amount of frames (so far) for Recording
|
||||
*/
|
||||
std::tuple<TasState, size_t, size_t> GetStatus() const;
|
||||
|
||||
// Retuns an array of the default button mappings
|
||||
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
|
||||
|
||||
// Retuns an array of the default analog mappings
|
||||
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
|
||||
[[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
|
||||
|
||||
private:
|
||||
struct TASCommand {
|
||||
u32 buttons{};
|
||||
TasAnalog l_axis{};
|
||||
TasAnalog r_axis{};
|
||||
};
|
||||
|
||||
// Loads TAS files from all players
|
||||
void LoadTasFiles();
|
||||
|
||||
// Loads TAS file from the specified player
|
||||
void LoadTasFile(size_t player_index);
|
||||
|
||||
// Writes a TAS file from the recorded commands
|
||||
void WriteTasFile(std::u8string file_name);
|
||||
|
||||
/**
|
||||
* Parses a string containing the axis values with the following format "x;y"
|
||||
* X and Y have a range from -32767 to 32767
|
||||
* @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
|
||||
*/
|
||||
TasAnalog ReadCommandAxis(const std::string& line) const;
|
||||
|
||||
/**
|
||||
* Parses a string containing the button values with the following format "a;b;c;d..."
|
||||
* Each button is represented by it's text format specified in text_to_tas_button array
|
||||
* @return Returns a u32 with each bit representing the status of a button
|
||||
*/
|
||||
u32 ReadCommandButtons(const std::string& line) const;
|
||||
|
||||
/**
|
||||
* Converts an u32 containing the button status into the text equivalent
|
||||
* @return Returns a string with the name of the buttons to be written to the file
|
||||
*/
|
||||
std::string WriteCommandButtons(u32 data) const;
|
||||
|
||||
/**
|
||||
* Converts an TAS analog object containing the axis status into the text equivalent
|
||||
* @return Returns a string with the value of the axis to be written to the file
|
||||
*/
|
||||
std::string WriteCommandAxis(TasAnalog data) const;
|
||||
|
||||
// Inverts the Y axis polarity
|
||||
std::pair<float, float> FlipAxisY(std::pair<float, float> old);
|
||||
|
||||
/**
|
||||
* Converts an u32 containing the button status into the text equivalent
|
||||
* @return Returns a string with the name of the buttons to be printed on console
|
||||
*/
|
||||
std::string DebugButtons(u32 buttons) const;
|
||||
|
||||
/**
|
||||
* Converts an TAS analog object containing the axis status into the text equivalent
|
||||
* @return Returns a string with the value of the axis to be printed on console
|
||||
*/
|
||||
std::string DebugJoystick(float x, float y) const;
|
||||
|
||||
/**
|
||||
* Converts the given TAS status into the text equivalent
|
||||
* @return Returns a string with the value of the TAS status to be printed on console
|
||||
*/
|
||||
std::string DebugInput(const TasData& data) const;
|
||||
|
||||
/**
|
||||
* Converts the given TAS status of multiple players into the text equivalent
|
||||
* @return Returns a string with the value of the status of all TAS players to be printed on
|
||||
* console
|
||||
*/
|
||||
std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
|
||||
|
||||
/**
|
||||
* Converts an u32 containing the button status into the text equivalent
|
||||
* @return Returns a string with the name of the buttons
|
||||
*/
|
||||
std::string ButtonsToString(u32 button) const;
|
||||
|
||||
// Stores current controller configuration and sets a TAS controller for every active controller
|
||||
// to the current config
|
||||
void SwapToTasController();
|
||||
|
||||
// Sets the stored controller configuration to the current config
|
||||
void SwapToStoredController();
|
||||
|
||||
size_t script_length{0};
|
||||
std::array<TasData, PLAYER_NUMBER> tas_data;
|
||||
bool is_old_input_saved{false};
|
||||
bool is_recording{false};
|
||||
bool is_running{false};
|
||||
bool needs_reset{false};
|
||||
std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
|
||||
std::vector<TASCommand> record_commands{};
|
||||
size_t current_command{0};
|
||||
TASCommand last_input{}; // only used for recording
|
||||
|
||||
// Old settings for swapping controllers
|
||||
std::array<Settings::PlayerInput, 10> player_mappings;
|
||||
};
|
||||
} // namespace TasInput
|
@@ -1,101 +0,0 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/tas/tas_input.h"
|
||||
#include "input_common/tas/tas_poller.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class TasButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
|
||||
: button(button_), pad(pad_), tas_input(tas_input_) {}
|
||||
|
||||
bool GetStatus() const override {
|
||||
return (tas_input->GetTasState(pad).buttons & button) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 button;
|
||||
const u32 pad;
|
||||
const TasInput::Tas* tas_input;
|
||||
};
|
||||
|
||||
TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
|
||||
: tas_input(std::move(tas_input_)) {}
|
||||
|
||||
std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
|
||||
const auto button_id = params.Get("button", 0);
|
||||
const auto pad = params.Get("pad", 0);
|
||||
|
||||
return std::make_unique<TasButton>(button_id, pad, tas_input.get());
|
||||
}
|
||||
|
||||
class TasAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
|
||||
: pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
|
||||
|
||||
float GetAxis(u32 axis) const {
|
||||
std::lock_guard lock{mutex};
|
||||
return tas_input->GetTasState(pad).axis.at(axis);
|
||||
}
|
||||
|
||||
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 {
|
||||
return GetAnalog(axis_x, axis_y);
|
||||
}
|
||||
|
||||
Input::AnalogProperties GetAnalogProperties() const override {
|
||||
return {0.0f, 1.0f, 0.5f};
|
||||
}
|
||||
|
||||
private:
|
||||
const u32 pad;
|
||||
const u32 axis_x;
|
||||
const u32 axis_y;
|
||||
const TasInput::Tas* tas_input;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from GC Adapter
|
||||
TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
|
||||
: tas_input(std::move(tas_input_)) {}
|
||||
|
||||
/**
|
||||
* 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> TasAnalogFactory::Create(const Common::ParamPackage& params) {
|
||||
const auto pad = static_cast<u32>(params.Get("pad", 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));
|
||||
|
||||
return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,43 +0,0 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/tas/tas_input.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/**
|
||||
* A button device factory representing a tas bot. It receives tas events and forward them
|
||||
* to all button devices it created.
|
||||
*/
|
||||
class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
|
||||
public:
|
||||
explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
|
||||
|
||||
/**
|
||||
* Creates a button device from a button press
|
||||
* @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;
|
||||
|
||||
private:
|
||||
std::shared_ptr<TasInput::Tas> tas_input;
|
||||
};
|
||||
|
||||
/// An analog device factory that creates analog devices from tas
|
||||
class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
|
||||
public:
|
||||
explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
|
||||
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<TasInput::Tas> tas_input;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,53 +0,0 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "input_common/touch_from_button.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class TouchFromButtonDevice final : public Input::TouchDevice {
|
||||
public:
|
||||
TouchFromButtonDevice() {
|
||||
const auto button_index =
|
||||
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
|
||||
const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
|
||||
|
||||
for (const auto& config_entry : buttons) {
|
||||
const Common::ParamPackage package{config_entry};
|
||||
map.emplace_back(
|
||||
Input::CreateDevice<Input::ButtonDevice>(config_entry),
|
||||
std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
|
||||
std::clamp(package.Get("y", 0), 0,
|
||||
static_cast<int>(Layout::ScreenUndocked::Height)));
|
||||
}
|
||||
}
|
||||
|
||||
Input::TouchStatus GetStatus() const override {
|
||||
Input::TouchStatus touch_status{};
|
||||
for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) {
|
||||
const bool state = std::get<0>(map[id])->GetStatus();
|
||||
if (state) {
|
||||
const float x = static_cast<float>(std::get<1>(map[id])) /
|
||||
static_cast<int>(Layout::ScreenUndocked::Width);
|
||||
const float y = static_cast<float>(std::get<2>(map[id])) /
|
||||
static_cast<int>(Layout::ScreenUndocked::Height);
|
||||
touch_status[id] = {x, y, true};
|
||||
}
|
||||
}
|
||||
return touch_status;
|
||||
}
|
||||
|
||||
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&) {
|
||||
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,526 +0,0 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.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, 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));
|
||||
SanitizeMotion(pad_data);
|
||||
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::PadData::Flags::AllPorts,
|
||||
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());
|
||||
}
|
||||
|
||||
void SanitizeMotion(Response::PadData& data) {
|
||||
// Zero out any non number value
|
||||
if (!std::isnormal(data.gyro.pitch)) {
|
||||
data.gyro.pitch = 0;
|
||||
}
|
||||
if (!std::isnormal(data.gyro.roll)) {
|
||||
data.gyro.roll = 0;
|
||||
}
|
||||
if (!std::isnormal(data.gyro.yaw)) {
|
||||
data.gyro.yaw = 0;
|
||||
}
|
||||
if (!std::isnormal(data.accel.x)) {
|
||||
data.accel.x = 0;
|
||||
}
|
||||
if (!std::isnormal(data.accel.y)) {
|
||||
data.accel.y = 0;
|
||||
}
|
||||
if (!std::isnormal(data.accel.z)) {
|
||||
data.accel.z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Client::Client() {
|
||||
LOG_INFO(Input, "Udp Initialization started");
|
||||
finger_id.fill(MAX_TOUCH_FINGERS);
|
||||
ReloadSockets();
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
Client::ClientConnection::ClientConnection() = default;
|
||||
|
||||
Client::ClientConnection::~ClientConnection() = default;
|
||||
|
||||
std::vector<Common::ParamPackage> Client::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
for (std::size_t pad = 0; pad < pads.size(); pad++) {
|
||||
if (!DeviceConnected(pad)) {
|
||||
continue;
|
||||
}
|
||||
std::string name = fmt::format("UDP Controller {}", pad);
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"class", "cemuhookudp"},
|
||||
{"display", std::move(name)},
|
||||
{"port", std::to_string(pad)},
|
||||
});
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
bool Client::DeviceConnected(std::size_t pad) const {
|
||||
// Use last timestamp to detect if the socket has stopped sending data
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_difference = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count());
|
||||
return time_difference < 1000 && pads[pad].connected;
|
||||
}
|
||||
|
||||
void Client::ReloadSockets() {
|
||||
Reset();
|
||||
|
||||
std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers));
|
||||
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 Client::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;
|
||||
}
|
||||
|
||||
void Client::OnVersion([[maybe_unused]] Response::Version data) {
|
||||
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
||||
}
|
||||
|
||||
void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
|
||||
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
|
||||
}
|
||||
|
||||
void Client::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;
|
||||
|
||||
const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
|
||||
pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
|
||||
// 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
|
||||
pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f);
|
||||
pads[pad_index].motion.UpdateRotation(time_difference);
|
||||
pads[pad_index].motion.UpdateOrientation(time_difference);
|
||||
|
||||
{
|
||||
std::lock_guard guard(pads[pad_index].status.update_mutex);
|
||||
pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion();
|
||||
|
||||
for (std::size_t id = 0; id < data.touch.size(); ++id) {
|
||||
UpdateTouchInput(data.touch[id], client, id);
|
||||
}
|
||||
|
||||
if (configuring) {
|
||||
const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope();
|
||||
const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration();
|
||||
UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::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].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()};
|
||||
|
||||
// Set motion parameters
|
||||
// SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
|
||||
// Real HW values are unknown, 0.0001 is an approximate to Standard
|
||||
for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) {
|
||||
pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::Reset() {
|
||||
for (auto& client : clients) {
|
||||
if (client.thread.joinable()) {
|
||||
client.active = -1;
|
||||
client.socket->Stop();
|
||||
client.thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
|
||||
const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) {
|
||||
if (gyro.Length() > 0.2f) {
|
||||
LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client,
|
||||
gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]);
|
||||
}
|
||||
UDPPadStatus pad{
|
||||
.host = clients[client].host,
|
||||
.port = clients[client].port,
|
||||
.pad_index = pad_index,
|
||||
};
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
|
||||
pad.motion = static_cast<PadMotion>(i);
|
||||
pad.motion_value = gyro[i];
|
||||
pad_queue.Push(pad);
|
||||
}
|
||||
if (acc[i] > 1.75f || acc[i] < -1.75f) {
|
||||
pad.motion = static_cast<PadMotion>(i + 3);
|
||||
pad.motion_value = acc[i];
|
||||
pad_queue.Push(pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::size_t> Client::GetUnusedFingerID() const {
|
||||
std::size_t first_free_id = 0;
|
||||
while (first_free_id < MAX_TOUCH_FINGERS) {
|
||||
if (!std::get<2>(touch_status[first_free_id])) {
|
||||
return first_free_id;
|
||||
} else {
|
||||
first_free_id++;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
|
||||
// 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 std::size_t touch_id = client * 2 + id;
|
||||
if (touch_pad.is_active) {
|
||||
if (finger_id[touch_id] == MAX_TOUCH_FINGERS) {
|
||||
const auto first_free_id = GetUnusedFingerID();
|
||||
if (!first_free_id) {
|
||||
// Invalid finger id skip to next input
|
||||
return;
|
||||
}
|
||||
finger_id[touch_id] = *first_free_id;
|
||||
}
|
||||
auto& [x, y, pressed] = touch_status[finger_id[touch_id]];
|
||||
x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
|
||||
static_cast<float>(max_x - min_x);
|
||||
y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
|
||||
static_cast<float>(max_y - min_y);
|
||||
pressed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (finger_id[touch_id] != MAX_TOUCH_FINGERS) {
|
||||
touch_status[finger_id[touch_id]] = {};
|
||||
finger_id[touch_id] = MAX_TOUCH_FINGERS;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::BeginConfiguration() {
|
||||
pad_queue.Clear();
|
||||
configuring = true;
|
||||
}
|
||||
|
||||
void Client::EndConfiguration() {
|
||||
pad_queue.Clear();
|
||||
configuring = false;
|
||||
}
|
||||
|
||||
DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) {
|
||||
const std::size_t client_number = GetClientNumber(host, port);
|
||||
if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
|
||||
return pads[0].status;
|
||||
}
|
||||
return pads[(client_number * PADS_PER_CLIENT) + pad].status;
|
||||
}
|
||||
|
||||
const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const {
|
||||
const std::size_t client_number = GetClientNumber(host, port);
|
||||
if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
|
||||
return pads[0].status;
|
||||
}
|
||||
return pads[(client_number * PADS_PER_CLIENT) + pad].status;
|
||||
}
|
||||
|
||||
Input::TouchStatus& Client::GetTouchState() {
|
||||
return touch_status;
|
||||
}
|
||||
|
||||
const Input::TouchStatus& Client::GetTouchState() const {
|
||||
return touch_status;
|
||||
}
|
||||
|
||||
Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() {
|
||||
return pad_queue;
|
||||
}
|
||||
|
||||
const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
|
||||
return pad_queue;
|
||||
}
|
||||
|
||||
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] {
|
||||
Status current_status{Status::Initialized};
|
||||
SocketCallback callback{
|
||||
[](Response::Version) {}, [](Response::PortInfo) {},
|
||||
[&](Response::PadData data) {
|
||||
static constexpr u16 CALIBRATION_THRESHOLD = 100;
|
||||
static constexpr u16 MAX_VALUE = UINT16_MAX;
|
||||
|
||||
if (current_status == Status::Initialized) {
|
||||
// Receiving data means the communication is ready now
|
||||
current_status = Status::Ready;
|
||||
status_callback(current_status);
|
||||
}
|
||||
const auto& touchpad_0 = data.touch[0];
|
||||
if (touchpad_0.is_active == 0) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
|
||||
const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
|
||||
const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
|
||||
if (current_status == Status::Ready) {
|
||||
// First touch - min data (min_x/min_y)
|
||||
current_status = Status::Stage1Completed;
|
||||
status_callback(current_status);
|
||||
}
|
||||
if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
|
||||
touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
|
||||
// Set the current position as max value and finishes configuration
|
||||
const u16 max_x = touchpad_0.x;
|
||||
const u16 max_y = touchpad_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
|
@@ -1,183 +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/param_package.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "input_common/motion_input.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
class Socket;
|
||||
|
||||
namespace Response {
|
||||
struct PadData;
|
||||
struct PortInfo;
|
||||
struct TouchPad;
|
||||
struct Version;
|
||||
} // namespace Response
|
||||
|
||||
enum class PadMotion {
|
||||
GyroX,
|
||||
GyroY,
|
||||
GyroZ,
|
||||
AccX,
|
||||
AccY,
|
||||
AccZ,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadTouch {
|
||||
Click,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
struct UDPPadStatus {
|
||||
std::string host{"127.0.0.1"};
|
||||
u16 port{26760};
|
||||
std::size_t pad_index{};
|
||||
PadMotion motion{PadMotion::Undefined};
|
||||
f32 motion_value{0.0f};
|
||||
};
|
||||
|
||||
struct DeviceStatus {
|
||||
std::mutex update_mutex;
|
||||
Input::MotionStatus 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:
|
||||
// Initialize the UDP client capture and read sequence
|
||||
Client();
|
||||
|
||||
// Close and release the client
|
||||
~Client();
|
||||
|
||||
// Used for polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const;
|
||||
|
||||
bool DeviceConnected(std::size_t pad) const;
|
||||
void ReloadSockets();
|
||||
|
||||
Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
|
||||
const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
|
||||
|
||||
DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad);
|
||||
const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const;
|
||||
|
||||
Input::TouchStatus& GetTouchState();
|
||||
const Input::TouchStatus& GetTouchState() const;
|
||||
|
||||
private:
|
||||
struct PadData {
|
||||
std::size_t pad_index{};
|
||||
bool connected{};
|
||||
DeviceStatus status;
|
||||
u64 packet_sequence{};
|
||||
|
||||
// Realtime values
|
||||
// motion is initalized with PID values for drift correction on joycons
|
||||
InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
};
|
||||
|
||||
struct ClientConnection {
|
||||
ClientConnection();
|
||||
~ClientConnection();
|
||||
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;
|
||||
|
||||
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);
|
||||
void UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
|
||||
const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro);
|
||||
|
||||
// Returns an unused finger id, if there is no fingers available std::nullopt will be
|
||||
// returned
|
||||
std::optional<std::size_t> GetUnusedFingerID() const;
|
||||
|
||||
// Merges and updates all touch inputs into the touch_status array
|
||||
void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
|
||||
|
||||
bool configuring = false;
|
||||
|
||||
// Allocate clients for 8 udp servers
|
||||
static constexpr std::size_t MAX_UDP_CLIENTS = 8;
|
||||
static constexpr std::size_t PADS_PER_CLIENT = 4;
|
||||
// Each client can have up 2 touch inputs
|
||||
static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
|
||||
std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
|
||||
std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
|
||||
Common::SPSCQueue<UDPPadStatus> pad_queue{};
|
||||
Input::TouchStatus touch_status{};
|
||||
std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
|
||||
};
|
||||
|
||||
/// 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
|
@@ -1,78 +0,0 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/udp/protocol.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
static constexpr std::size_t GetSizeOfResponseType(Type t) {
|
||||
switch (t) {
|
||||
case Type::Version:
|
||||
return sizeof(Response::Version);
|
||||
case Type::PortInfo:
|
||||
return sizeof(Response::PortInfo);
|
||||
case Type::PadData:
|
||||
return sizeof(Response::PadData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace Response {
|
||||
|
||||
/**
|
||||
* Returns Type if the packet is valid, else none
|
||||
*
|
||||
* Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
|
||||
* copying the buffer)
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size) {
|
||||
if (size < sizeof(Header)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
Header header{};
|
||||
std::memcpy(&header, data, sizeof(Header));
|
||||
if (header.magic != SERVER_MAGIC) {
|
||||
LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.protocol_version != PROTOCOL_VERSION) {
|
||||
LOG_ERROR(Input, "UDP Packet protocol mismatch");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.type < Type::Version || header.type > Type::PadData) {
|
||||
LOG_ERROR(Input, "UDP Packet is an unknown type");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Packet size must equal sizeof(Header) + sizeof(Data)
|
||||
// and also verify that the packet info mentions the correct size. Since the spec includes the
|
||||
// type of the packet as part of the data, we need to include it in size calculations here
|
||||
// ie: payload_length == sizeof(T) + sizeof(Type)
|
||||
const std::size_t data_len = GetSizeOfResponseType(header.type);
|
||||
if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
|
||||
LOG_ERROR(
|
||||
Input,
|
||||
"UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
|
||||
size, header.payload_length, data_len + sizeof(Type));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const u32 crc32 = header.crc;
|
||||
boost::crc_32_type result;
|
||||
// zero out the crc in the buffer and then run the crc against it
|
||||
std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
|
||||
|
||||
result.process_bytes(data, data_len + sizeof(Header));
|
||||
if (crc32 != result.checksum()) {
|
||||
LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
|
||||
return std::nullopt;
|
||||
}
|
||||
return header.type;
|
||||
}
|
||||
} // namespace Response
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
@@ -1,259 +0,0 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
constexpr std::size_t MAX_PACKET_SIZE = 100;
|
||||
constexpr u16 PROTOCOL_VERSION = 1001;
|
||||
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
|
||||
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
|
||||
|
||||
enum class Type : u32 {
|
||||
Version = 0x00100000,
|
||||
PortInfo = 0x00100001,
|
||||
PadData = 0x00100002,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u32_le magic{};
|
||||
u16_le protocol_version{};
|
||||
u16_le payload_length{};
|
||||
u32_le crc{};
|
||||
u32_le id{};
|
||||
///> In the protocol, the type of the packet is not part of the header, but its convenient to
|
||||
///> include in the header so the callee doesn't have to duplicate the type twice when building
|
||||
///> the data
|
||||
Type type{};
|
||||
};
|
||||
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
template <typename T>
|
||||
struct Message {
|
||||
Header header{};
|
||||
T data;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename T>
|
||||
constexpr Type GetMessageType();
|
||||
|
||||
namespace Request {
|
||||
|
||||
struct Version {};
|
||||
/**
|
||||
* 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
|
||||
* request explicitly for the first controller port and leave it at that. In the future it would be
|
||||
* nice to make this configurable
|
||||
*/
|
||||
constexpr u32 MAX_PORTS = 4;
|
||||
struct PortInfo {
|
||||
u32_le pad_count{}; ///> Number of ports to request data for
|
||||
std::array<u8, MAX_PORTS> port;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"UDP Request PortInfo is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Request the latest pad information from the server. If the server hasn't received this message
|
||||
* from the client in a reasonable time frame, the server will stop sending updates. The default
|
||||
* timeout seems to be 5 seconds.
|
||||
*/
|
||||
struct PadData {
|
||||
enum class Flags : u8 {
|
||||
AllPorts,
|
||||
Id,
|
||||
Mac,
|
||||
};
|
||||
/// Determines which method will be used as a look up for the controller
|
||||
Flags flags{};
|
||||
/// Index of the port of the controller to retrieve data about
|
||||
u8 port_id{};
|
||||
/// Mac address of the controller to retrieve data about
|
||||
MacAddress mac;
|
||||
};
|
||||
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Request PadData is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Creates a message with the proper header data that can be sent to the server.
|
||||
* @param data Request body to send
|
||||
* @param client_id ID of the udp client (usually not checked on the server)
|
||||
*/
|
||||
template <typename T>
|
||||
Message<T> Create(const T data, const u32 client_id = 0) {
|
||||
boost::crc_32_type crc;
|
||||
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 Response {
|
||||
|
||||
struct Version {
|
||||
u16_le version{};
|
||||
};
|
||||
static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Version>,
|
||||
"UDP Response Version is not trivially copyable");
|
||||
|
||||
struct PortInfo {
|
||||
u8 id{};
|
||||
u8 state{};
|
||||
u8 model{};
|
||||
u8 connection_type{};
|
||||
MacAddress mac;
|
||||
u8 battery{};
|
||||
u8 is_pad_active{};
|
||||
};
|
||||
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"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)
|
||||
struct PadData {
|
||||
PortInfo info{};
|
||||
u32_le packet_counter{};
|
||||
|
||||
u16_le digital_button{};
|
||||
// The following union isn't trivially copyable but we don't use this input anyway.
|
||||
// union DigitalButton {
|
||||
// u16_le button;
|
||||
// BitField<0, 1, u16> button_1; // Share
|
||||
// BitField<1, 1, u16> button_2; // L3
|
||||
// BitField<2, 1, u16> button_3; // R3
|
||||
// BitField<3, 1, u16> button_4; // Options
|
||||
// BitField<4, 1, u16> button_5; // Up
|
||||
// BitField<5, 1, u16> button_6; // Right
|
||||
// BitField<6, 1, u16> button_7; // Down
|
||||
// BitField<7, 1, u16> button_8; // Left
|
||||
// BitField<8, 1, u16> button_9; // L2
|
||||
// BitField<9, 1, u16> button_10; // R2
|
||||
// BitField<10, 1, u16> button_11; // L1
|
||||
// BitField<11, 1, u16> button_12; // R1
|
||||
// BitField<12, 1, u16> button_13; // Triangle
|
||||
// BitField<13, 1, u16> button_14; // Circle
|
||||
// BitField<14, 1, u16> button_15; // Cross
|
||||
// BitField<15, 1, u16> button_16; // Square
|
||||
// } digital_button;
|
||||
|
||||
u8 home;
|
||||
/// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
|
||||
u8 touch_hard_press{};
|
||||
u8 left_stick_x{};
|
||||
u8 left_stick_y{};
|
||||
u8 right_stick_x{};
|
||||
u8 right_stick_y{};
|
||||
|
||||
struct AnalogButton {
|
||||
u8 button_8{};
|
||||
u8 button_7{};
|
||||
u8 button_6{};
|
||||
u8 button_5{};
|
||||
u8 button_12{};
|
||||
u8 button_11{};
|
||||
u8 button_10{};
|
||||
u8 button_9{};
|
||||
u8 button_16{};
|
||||
u8 button_15{};
|
||||
u8 button_14{};
|
||||
u8 button_13{};
|
||||
} analog_button;
|
||||
|
||||
std::array<TouchPad, 2> touch;
|
||||
|
||||
u64_le motion_timestamp;
|
||||
|
||||
struct Accelerometer {
|
||||
float x{};
|
||||
float y{};
|
||||
float z{};
|
||||
} accel;
|
||||
|
||||
struct Gyroscope {
|
||||
float pitch{};
|
||||
float yaw{};
|
||||
float roll{};
|
||||
} gyro;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Response PadData is not trivially copyable");
|
||||
|
||||
static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
|
||||
"UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
|
||||
|
||||
static_assert(sizeof(PadData::AnalogButton) == 12,
|
||||
"UDP Response AnalogButton struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Accelerometer) == 12,
|
||||
"UDP Response Accelerometer struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
|
||||
|
||||
/**
|
||||
* Create a Response Message from the data
|
||||
* @param data array of bytes sent from the server
|
||||
* @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
|
||||
* copy the data into the appropriate struct for that Type
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size);
|
||||
|
||||
} // namespace Response
|
||||
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
} // namespace InputCommon::CemuhookUDP
|
@@ -1,110 +0,0 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "input_common/udp/udp.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class UDPMotion final : public Input::MotionDevice {
|
||||
public:
|
||||
explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
|
||||
: ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
|
||||
|
||||
Input::MotionStatus GetStatus() const override {
|
||||
return client->GetPadState(ip, port, pad).motion_status;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string ip;
|
||||
const u16 port;
|
||||
const u16 pad;
|
||||
CemuhookUDP::Client* client;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/// A motion device factory that creates motion devices from a UDP client
|
||||
UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
|
||||
: client(std::move(client_)) {}
|
||||
|
||||
/**
|
||||
* Creates motion device
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "port": the UDP port number
|
||||
*/
|
||||
std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
|
||||
auto ip = params.Get("ip", "127.0.0.1");
|
||||
const auto port = static_cast<u16>(params.Get("port", 26760));
|
||||
const auto pad = static_cast<u16>(params.Get("pad_index", 0));
|
||||
|
||||
return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
|
||||
}
|
||||
|
||||
void UDPMotionFactory::BeginConfiguration() {
|
||||
polling = true;
|
||||
client->BeginConfiguration();
|
||||
}
|
||||
|
||||
void UDPMotionFactory::EndConfiguration() {
|
||||
polling = false;
|
||||
client->EndConfiguration();
|
||||
}
|
||||
|
||||
Common::ParamPackage UDPMotionFactory::GetNextInput() {
|
||||
Common::ParamPackage params;
|
||||
CemuhookUDP::UDPPadStatus pad;
|
||||
auto& queue = client->GetPadQueue();
|
||||
while (queue.Pop(pad)) {
|
||||
if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
|
||||
continue;
|
||||
}
|
||||
params.Set("engine", "cemuhookudp");
|
||||
params.Set("ip", pad.host);
|
||||
params.Set("port", static_cast<u16>(pad.port));
|
||||
params.Set("pad_index", static_cast<u16>(pad.pad_index));
|
||||
params.Set("motion", static_cast<u16>(pad.motion));
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
class UDPTouch final : public Input::TouchDevice {
|
||||
public:
|
||||
explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
|
||||
: ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
|
||||
|
||||
Input::TouchStatus GetStatus() const override {
|
||||
return client->GetTouchState();
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string ip;
|
||||
[[maybe_unused]] const u16 port;
|
||||
[[maybe_unused]] const u16 pad;
|
||||
CemuhookUDP::Client* client;
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
/// A motion device factory that creates motion devices from a UDP client
|
||||
UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
|
||||
: client(std::move(client_)) {}
|
||||
|
||||
/**
|
||||
* Creates motion device
|
||||
* @param params contains parameters for creating the device:
|
||||
* - "port": the UDP port number
|
||||
*/
|
||||
std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
|
||||
auto ip = params.Get("ip", "127.0.0.1");
|
||||
const auto port = static_cast<u16>(params.Get("port", 26760));
|
||||
const auto pad = static_cast<u16>(params.Get("pad_index", 0));
|
||||
|
||||
return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
@@ -1,57 +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 "core/frontend/input.h"
|
||||
#include "input_common/udp/client.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
/// A motion device factory that creates motion devices from udp clients
|
||||
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
||||
public:
|
||||
explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
|
||||
|
||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::ParamPackage GetNextInput();
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<CemuhookUDP::Client> client;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
/// A touch device factory that creates touch devices from udp clients
|
||||
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
|
||||
public:
|
||||
explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
|
||||
|
||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
|
||||
|
||||
Common::ParamPackage GetNextInput();
|
||||
|
||||
/// For device input configuration/polling
|
||||
void BeginConfiguration();
|
||||
void EndConfiguration();
|
||||
|
||||
bool IsPolling() const {
|
||||
return polling;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<CemuhookUDP::Client> client;
|
||||
bool polling = false;
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
Reference in New Issue
Block a user