Add cemu hook changes related to PR #4609
This commit is contained in:
parent
0774b17846
commit
6ee8eab670
|
@ -397,8 +397,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
|
||||||
std::tie(motion_devices[e].accel, motion_devices[e].gyro,
|
std::tie(motion_devices[e].accel, motion_devices[e].gyro,
|
||||||
motion_devices[e].rotation, motion_devices[e].orientation) =
|
motion_devices[e].rotation, motion_devices[e].orientation) =
|
||||||
device->GetStatus();
|
device->GetStatus();
|
||||||
sixaxis_at_rest =
|
sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
|
||||||
sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.00005f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
#include "input_common/motion_emu.h"
|
#include "input_common/motion_emu.h"
|
||||||
#include "input_common/touch_from_button.h"
|
#include "input_common/touch_from_button.h"
|
||||||
|
#include "input_common/udp/client.h"
|
||||||
#include "input_common/udp/udp.h"
|
#include "input_common/udp/udp.h"
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
#include "input_common/sdl/sdl.h"
|
#include "input_common/sdl/sdl.h"
|
||||||
|
@ -40,7 +41,11 @@ struct InputSubsystem::Impl {
|
||||||
sdl = SDL::Init();
|
sdl = SDL::Init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
udp = CemuhookUDP::Init();
|
udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
|
||||||
|
udpmotion = std::make_shared<UDPMotionFactory>(udp);
|
||||||
|
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
|
||||||
|
udptouch = std::make_shared<UDPTouchFactory>(udp);
|
||||||
|
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
|
@ -53,12 +58,17 @@ struct InputSubsystem::Impl {
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
sdl.reset();
|
sdl.reset();
|
||||||
#endif
|
#endif
|
||||||
udp.reset();
|
|
||||||
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
|
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
|
||||||
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
|
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
|
||||||
|
|
||||||
gcbuttons.reset();
|
gcbuttons.reset();
|
||||||
gcanalog.reset();
|
gcanalog.reset();
|
||||||
|
|
||||||
|
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
|
||||||
|
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
|
||||||
|
|
||||||
|
udpmotion.reset();
|
||||||
|
udptouch.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
|
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
|
||||||
|
@ -109,14 +119,28 @@ struct InputSubsystem::Impl {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
|
||||||
|
const Common::ParamPackage& params) const {
|
||||||
|
if (!params.Has("class") || params.Get("class", "") == "any") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (params.Get("class", "") == "cemuhookudp") {
|
||||||
|
// TODO return the correct motion device
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Keyboard> keyboard;
|
std::shared_ptr<Keyboard> keyboard;
|
||||||
std::shared_ptr<MotionEmu> motion_emu;
|
std::shared_ptr<MotionEmu> motion_emu;
|
||||||
#ifdef HAVE_SDL2
|
#ifdef HAVE_SDL2
|
||||||
std::unique_ptr<SDL::State> sdl;
|
std::unique_ptr<SDL::State> sdl;
|
||||||
#endif
|
#endif
|
||||||
std::unique_ptr<CemuhookUDP::State> udp;
|
|
||||||
std::shared_ptr<GCButtonFactory> gcbuttons;
|
std::shared_ptr<GCButtonFactory> gcbuttons;
|
||||||
std::shared_ptr<GCAnalogFactory> gcanalog;
|
std::shared_ptr<GCAnalogFactory> gcanalog;
|
||||||
|
std::shared_ptr<UDPMotionFactory> udpmotion;
|
||||||
|
std::shared_ptr<UDPTouchFactory> udptouch;
|
||||||
|
std::shared_ptr<CemuhookUDP::Client> udp;
|
||||||
};
|
};
|
||||||
|
|
||||||
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
|
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
|
||||||
|
@ -175,6 +199,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
|
||||||
return impl->gcbuttons.get();
|
return impl->gcbuttons.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UDPMotionFactory* InputSubsystem::GetUDPMotions() {
|
||||||
|
return impl->udpmotion.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
|
||||||
|
return impl->udpmotion.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
UDPTouchFactory* InputSubsystem::GetUDPTouch() {
|
||||||
|
return impl->udptouch.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
|
||||||
|
return impl->udptouch.get();
|
||||||
|
}
|
||||||
|
|
||||||
void InputSubsystem::ReloadInputDevices() {
|
void InputSubsystem::ReloadInputDevices() {
|
||||||
if (!impl->udp) {
|
if (!impl->udp) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -54,6 +54,8 @@ public:
|
||||||
|
|
||||||
class GCAnalogFactory;
|
class GCAnalogFactory;
|
||||||
class GCButtonFactory;
|
class GCButtonFactory;
|
||||||
|
class UDPMotionFactory;
|
||||||
|
class UDPTouchFactory;
|
||||||
class Keyboard;
|
class Keyboard;
|
||||||
class MotionEmu;
|
class MotionEmu;
|
||||||
|
|
||||||
|
@ -123,6 +125,18 @@ public:
|
||||||
/// Retrieves the underlying GameCube button handler.
|
/// Retrieves the underlying GameCube button handler.
|
||||||
[[nodiscard]] const GCButtonFactory* GetGCButtons() const;
|
[[nodiscard]] const GCButtonFactory* GetGCButtons() const;
|
||||||
|
|
||||||
|
/// Retrieves the underlying udp motion handler.
|
||||||
|
[[nodiscard]] UDPMotionFactory* GetUDPMotions();
|
||||||
|
|
||||||
|
/// Retrieves the underlying udp motion handler.
|
||||||
|
[[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
|
||||||
|
|
||||||
|
/// Retrieves the underlying udp touch handler.
|
||||||
|
[[nodiscard]] UDPTouchFactory* GetUDPTouch();
|
||||||
|
|
||||||
|
/// Retrieves the underlying udp touch handler.
|
||||||
|
[[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
|
||||||
|
|
||||||
/// Reloads the input devices
|
/// Reloads the input devices
|
||||||
void ReloadInputDevices();
|
void ReloadInputDevices();
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/settings.h"
|
||||||
#include "input_common/udp/client.h"
|
#include "input_common/udp/client.h"
|
||||||
#include "input_common/udp/protocol.h"
|
#include "input_common/udp/protocol.h"
|
||||||
|
|
||||||
|
@ -131,21 +130,60 @@ static void SocketLoop(Socket* socket) {
|
||||||
socket->Loop();
|
socket->Loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
|
Client::Client() {
|
||||||
u8 pad_index, u32 client_id)
|
LOG_INFO(Input, "Udp Initialization started");
|
||||||
: status(std::move(status)) {
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
StartCommunication(host, port, pad_index, client_id);
|
u8 pad = client % 4;
|
||||||
|
StartCommunication(client, Settings::values.udp_input_address,
|
||||||
|
Settings::values.udp_input_port, pad, 24872);
|
||||||
|
// Set motion parameters
|
||||||
|
// SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
|
||||||
|
// Real HW values are unkown, 0.0001 is an aproximate to Standard
|
||||||
|
clients[client].motion.SetGyroThreshold(0.0001f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Client::~Client() {
|
Client::~Client() {
|
||||||
socket->Stop();
|
Reset();
|
||||||
thread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> Client::GetInputDevices() const {
|
||||||
|
std::vector<Common::ParamPackage> devices;
|
||||||
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
|
if (!DeviceConnected(client)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string name = fmt::format("UDP Controller{} {} {}", clients[client].active,
|
||||||
|
clients[client].active == 1, client);
|
||||||
|
devices.emplace_back(Common::ParamPackage{
|
||||||
|
{"class", "cemuhookudp"},
|
||||||
|
{"display", std::move(name)},
|
||||||
|
{"port", std::to_string(client)},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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::system_clock::now();
|
||||||
|
u64 time_difference =
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
|
||||||
|
.count();
|
||||||
|
return time_difference < 1000 && clients[pad].active == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::ReloadUDPClient() {
|
||||||
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
|
ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
|
||||||
|
}
|
||||||
|
}
|
||||||
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
||||||
socket->Stop();
|
// client number must be determined from host / port and pad index
|
||||||
thread.join();
|
std::size_t client = pad_index;
|
||||||
StartCommunication(host, port, pad_index, client_id);
|
clients[client].socket->Stop();
|
||||||
|
clients[client].thread.join();
|
||||||
|
StartCommunication(client, host, port, pad_index, client_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::OnVersion(Response::Version data) {
|
void Client::OnVersion(Response::Version data) {
|
||||||
|
@ -157,31 +195,39 @@ void Client::OnPortInfo(Response::PortInfo data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::OnPadData(Response::PadData data) {
|
void Client::OnPadData(Response::PadData data) {
|
||||||
|
// client number must be determined from host / port and pad index
|
||||||
|
std::size_t client = data.info.id;
|
||||||
LOG_TRACE(Input, "PadData packet received");
|
LOG_TRACE(Input, "PadData packet received");
|
||||||
if (data.packet_counter <= packet_sequence) {
|
if (data.packet_counter == clients[client].packet_sequence) {
|
||||||
LOG_WARNING(
|
LOG_WARNING(
|
||||||
Input,
|
Input,
|
||||||
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
||||||
packet_sequence, data.packet_counter);
|
clients[client].packet_sequence, data.packet_counter);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
packet_sequence = data.packet_counter;
|
clients[client].active = data.info.is_pad_active;
|
||||||
// TODO: Check how the Switch handles motions and how the CemuhookUDP motion
|
clients[client].packet_sequence = data.packet_counter;
|
||||||
// directions correspond to the ones of the Switch
|
const auto now = std::chrono::system_clock::now();
|
||||||
Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
|
u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
|
now - clients[client].last_motion_update)
|
||||||
|
.count();
|
||||||
|
clients[client].last_motion_update = now;
|
||||||
|
Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
|
||||||
|
clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
|
||||||
|
// Gyroscope values are not it the correct scale from better joy.
|
||||||
|
// By dividing by 312 allow us to make one full turn = 1 turn
|
||||||
|
// This must be a configurable valued called sensitivity
|
||||||
|
clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
|
||||||
|
clients[client].motion.UpdateRotation(time_difference);
|
||||||
|
clients[client].motion.UpdateOrientation(time_difference);
|
||||||
|
Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
|
||||||
|
Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
|
||||||
|
Common::Vec3f rotation = clients[client].motion.GetRotations();
|
||||||
|
std::array<Common::Vec3f, 3> orientation = clients[client].motion.GetOrientation();
|
||||||
|
|
||||||
// TODO: Calculate the correct rotation vector and orientation matrix
|
|
||||||
const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
|
|
||||||
const 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),
|
|
||||||
};
|
|
||||||
{
|
{
|
||||||
std::lock_guard guard(status->update_mutex);
|
std::lock_guard guard(clients[client].status.update_mutex);
|
||||||
|
clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation};
|
||||||
status->motion_status = {accel, gyro, rotation, orientation};
|
|
||||||
|
|
||||||
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
|
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
|
||||||
// between a simple "tap" and a hard press that causes the touch screen to click.
|
// between a simple "tap" and a hard press that causes the touch screen to click.
|
||||||
|
@ -190,11 +236,11 @@ void Client::OnPadData(Response::PadData data) {
|
||||||
float x = 0;
|
float x = 0;
|
||||||
float y = 0;
|
float y = 0;
|
||||||
|
|
||||||
if (is_active && status->touch_calibration) {
|
if (is_active && clients[client].status.touch_calibration) {
|
||||||
const u16 min_x = status->touch_calibration->min_x;
|
const u16 min_x = clients[client].status.touch_calibration->min_x;
|
||||||
const u16 max_x = status->touch_calibration->max_x;
|
const u16 max_x = clients[client].status.touch_calibration->max_x;
|
||||||
const u16 min_y = status->touch_calibration->min_y;
|
const u16 min_y = clients[client].status.touch_calibration->min_y;
|
||||||
const u16 max_y = status->touch_calibration->max_y;
|
const u16 max_y = clients[client].status.touch_calibration->max_y;
|
||||||
|
|
||||||
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
|
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
|
||||||
static_cast<float>(max_x - min_x);
|
static_cast<float>(max_x - min_x);
|
||||||
|
@ -202,17 +248,82 @@ void Client::OnPadData(Response::PadData data) {
|
||||||
static_cast<float>(max_y - min_y);
|
static_cast<float>(max_y - min_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
status->touch_status = {x, y, is_active};
|
clients[client].status.touch_status = {x, y, is_active};
|
||||||
|
|
||||||
|
if (configuring) {
|
||||||
|
UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
|
||||||
|
u32 client_id) {
|
||||||
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
||||||
[this](Response::PortInfo info) { OnPortInfo(info); },
|
[this](Response::PortInfo info) { OnPortInfo(info); },
|
||||||
[this](Response::PadData data) { OnPadData(data); }};
|
[this](Response::PadData data) { OnPadData(data); }};
|
||||||
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
||||||
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
|
clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
|
||||||
thread = std::thread{SocketLoop, this->socket.get()};
|
clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::Reset() {
|
||||||
|
for (std::size_t client = 0; client < clients.size(); client++) {
|
||||||
|
clients[client].socket->Stop();
|
||||||
|
clients[client].thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
|
||||||
|
const Common::Vec3<float>& gyro, bool touch) {
|
||||||
|
if (configuring) {
|
||||||
|
UDPPadStatus pad;
|
||||||
|
if (touch) {
|
||||||
|
pad.touch = PadTouch::Click;
|
||||||
|
pad_queue[client].Push(pad);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < 3; ++i) {
|
||||||
|
if (gyro[i] > 6.0f || gyro[i] < -6.0f) {
|
||||||
|
pad.motion = static_cast<PadMotion>(i);
|
||||||
|
pad.motion_value = gyro[i];
|
||||||
|
pad_queue[client].Push(pad);
|
||||||
|
}
|
||||||
|
if (acc[i] > 2.0f || acc[i] < -2.0f) {
|
||||||
|
pad.motion = static_cast<PadMotion>(i + 3);
|
||||||
|
pad.motion_value = acc[i];
|
||||||
|
pad_queue[client].Push(pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::BeginConfiguration() {
|
||||||
|
for (auto& pq : pad_queue) {
|
||||||
|
pq.Clear();
|
||||||
|
}
|
||||||
|
configuring = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::EndConfiguration() {
|
||||||
|
for (auto& pq : pad_queue) {
|
||||||
|
pq.Clear();
|
||||||
|
}
|
||||||
|
configuring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceStatus& Client::GetPadState(std::size_t pad) {
|
||||||
|
return clients[pad].status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeviceStatus& Client::GetPadState(std::size_t pad) const {
|
||||||
|
return clients[pad].status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
|
||||||
|
return pad_queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
|
||||||
|
return pad_queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||||
|
|
|
@ -12,9 +12,12 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/param_package.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
#include "common/threadsafe_queue.h"
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
|
#include "input_common/motion_input.h"
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
namespace InputCommon::CemuhookUDP {
|
||||||
|
|
||||||
|
@ -29,6 +32,27 @@ struct PortInfo;
|
||||||
struct Version;
|
struct Version;
|
||||||
} // namespace Response
|
} // namespace Response
|
||||||
|
|
||||||
|
enum class PadMotion {
|
||||||
|
GyroX,
|
||||||
|
GyroY,
|
||||||
|
GyroZ,
|
||||||
|
AccX,
|
||||||
|
AccY,
|
||||||
|
AccZ,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PadTouch {
|
||||||
|
Click,
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UDPPadStatus {
|
||||||
|
PadTouch touch{PadTouch::Undefined};
|
||||||
|
PadMotion motion{PadMotion::Undefined};
|
||||||
|
f32 motion_value{0.0f};
|
||||||
|
};
|
||||||
|
|
||||||
struct DeviceStatus {
|
struct DeviceStatus {
|
||||||
std::mutex update_mutex;
|
std::mutex update_mutex;
|
||||||
Input::MotionStatus motion_status;
|
Input::MotionStatus motion_status;
|
||||||
|
@ -46,22 +70,58 @@ struct DeviceStatus {
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
public:
|
public:
|
||||||
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
|
// Initialize the UDP client capture and read sequence
|
||||||
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
|
Client();
|
||||||
|
|
||||||
|
// Close and release the client
|
||||||
~Client();
|
~Client();
|
||||||
|
|
||||||
|
// Used for polling
|
||||||
|
void BeginConfiguration();
|
||||||
|
void EndConfiguration();
|
||||||
|
|
||||||
|
std::vector<Common::ParamPackage> GetInputDevices() const;
|
||||||
|
|
||||||
|
bool DeviceConnected(std::size_t pad) const;
|
||||||
|
void ReloadUDPClient();
|
||||||
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
|
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
|
||||||
u32 client_id = 24872);
|
u32 client_id = 24872);
|
||||||
|
|
||||||
|
std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
|
||||||
|
const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
|
||||||
|
|
||||||
|
DeviceStatus& GetPadState(std::size_t pad);
|
||||||
|
const DeviceStatus& GetPadState(std::size_t pad) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ClientData {
|
||||||
|
std::unique_ptr<Socket> socket;
|
||||||
|
DeviceStatus status;
|
||||||
|
std::thread thread;
|
||||||
|
u64 packet_sequence = 0;
|
||||||
|
u8 active;
|
||||||
|
|
||||||
|
// 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::system_clock> last_motion_update;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For shutting down, clear all data, join all threads, release usb
|
||||||
|
void Reset();
|
||||||
|
|
||||||
void OnVersion(Response::Version);
|
void OnVersion(Response::Version);
|
||||||
void OnPortInfo(Response::PortInfo);
|
void OnPortInfo(Response::PortInfo);
|
||||||
void OnPadData(Response::PadData);
|
void OnPadData(Response::PadData);
|
||||||
void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
|
void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
|
||||||
|
u32 client_id);
|
||||||
|
void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
|
||||||
|
const Common::Vec3<float>& gyro, bool touch);
|
||||||
|
|
||||||
std::unique_ptr<Socket> socket;
|
bool configuring = false;
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
std::thread thread;
|
std::array<ClientData, 4> clients;
|
||||||
u64 packet_sequence = 0;
|
std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An async job allowing configuration of the touchpad calibration.
|
/// An async job allowing configuration of the touchpad calibration.
|
||||||
|
|
|
@ -1,105 +1,144 @@
|
||||||
// Copyright 2018 Citra Emulator Project
|
// Copyright 2020 yuzu Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <utility>
|
||||||
#include <tuple>
|
#include "common/assert.h"
|
||||||
|
#include "common/threadsafe_queue.h"
|
||||||
#include "common/param_package.h"
|
|
||||||
#include "core/frontend/input.h"
|
|
||||||
#include "core/settings.h"
|
|
||||||
#include "input_common/udp/client.h"
|
#include "input_common/udp/client.h"
|
||||||
#include "input_common/udp/udp.h"
|
#include "input_common/udp/udp.h"
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
namespace InputCommon {
|
||||||
|
|
||||||
class UDPTouchDevice final : public Input::TouchDevice {
|
class UDPMotion final : public Input::MotionDevice {
|
||||||
public:
|
public:
|
||||||
explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
|
||||||
std::tuple<float, float, bool> GetStatus() const override {
|
: ip(ip_), port(port_), pad(pad_), client(client_) {}
|
||||||
std::lock_guard guard(status->update_mutex);
|
|
||||||
return status->touch_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UDPMotionDevice final : public Input::MotionDevice {
|
|
||||||
public:
|
|
||||||
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
|
||||||
Input::MotionStatus GetStatus() const override {
|
Input::MotionStatus GetStatus() const override {
|
||||||
std::lock_guard guard(status->update_mutex);
|
return client->GetPadState(pad).motion_status;
|
||||||
return status->motion_status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<DeviceStatus> status;
|
const std::string ip;
|
||||||
|
const int port;
|
||||||
|
const int pad;
|
||||||
|
CemuhookUDP::Client* client;
|
||||||
|
mutable std::mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
|
/// A motion device factory that creates motion devices from JC Adapter
|
||||||
public:
|
UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
|
||||||
explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
: client(std::move(client_)) {}
|
||||||
|
|
||||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
|
/**
|
||||||
{
|
* Creates motion device
|
||||||
std::lock_guard guard(status->update_mutex);
|
* @param params contains parameters for creating the device:
|
||||||
status->touch_calibration = DeviceStatus::CalibrationData{};
|
* - "port": the nth jcpad on the adapter
|
||||||
// These default values work well for DS4 but probably not other touch inputs
|
*/
|
||||||
status->touch_calibration->min_x = params.Get("min_x", 100);
|
std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
|
||||||
status->touch_calibration->min_y = params.Get("min_y", 50);
|
const std::string ip = params.Get("ip", "127.0.0.1");
|
||||||
status->touch_calibration->max_x = params.Get("max_x", 1800);
|
const int port = params.Get("port", 26760);
|
||||||
status->touch_calibration->max_y = params.Get("max_y", 850);
|
const int pad = params.Get("pad_index", 0);
|
||||||
|
|
||||||
|
return std::make_unique<UDPMotion>(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();
|
||||||
|
for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
|
||||||
|
while (queue[pad_number].Pop(pad)) {
|
||||||
|
if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
params.Set("engine", "cemuhookudp");
|
||||||
|
params.Set("ip", "127.0.0.1");
|
||||||
|
params.Set("port", 26760);
|
||||||
|
params.Set("pad_index", static_cast<int>(pad_number));
|
||||||
|
params.Set("motion", static_cast<u16>(pad.motion));
|
||||||
|
return params;
|
||||||
}
|
}
|
||||||
return std::make_unique<UDPTouchDevice>(status);
|
|
||||||
}
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
class UDPTouch final : public Input::TouchDevice {
|
||||||
std::shared_ptr<DeviceStatus> status;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
|
||||||
public:
|
public:
|
||||||
explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
|
||||||
|
: ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
|
||||||
|
|
||||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
|
std::tuple<float, float, bool> GetStatus() const override {
|
||||||
return std::make_unique<UDPMotionDevice>(status);
|
return client->GetPadState(pad).touch_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<DeviceStatus> status;
|
const std::string ip;
|
||||||
|
const int port;
|
||||||
|
const int pad;
|
||||||
|
CemuhookUDP::Client* client;
|
||||||
|
mutable std::mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
State::State() {
|
/// A motion device factory that creates motion devices from JC Adapter
|
||||||
auto status = std::make_shared<DeviceStatus>();
|
UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
|
||||||
client =
|
: client(std::move(client_)) {}
|
||||||
std::make_unique<Client>(status, Settings::values.udp_input_address,
|
|
||||||
Settings::values.udp_input_port, Settings::values.udp_pad_index);
|
|
||||||
|
|
||||||
motion_factory = std::make_shared<UDPMotionFactory>(status);
|
/**
|
||||||
touch_factory = std::make_shared<UDPTouchFactory>(status);
|
* Creates motion device
|
||||||
|
* @param params contains parameters for creating the device:
|
||||||
|
* - "port": the nth jcpad on the adapter
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
|
||||||
|
const std::string ip = params.Get("ip", "127.0.0.1");
|
||||||
|
const int port = params.Get("port", 26760);
|
||||||
|
const int pad = params.Get("pad_index", 0);
|
||||||
|
|
||||||
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory);
|
return std::make_unique<UDPTouch>(ip, port, pad, client.get());
|
||||||
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
State::~State() {
|
void UDPTouchFactory::BeginConfiguration() {
|
||||||
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
|
polling = true;
|
||||||
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
|
client->BeginConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Common::ParamPackage> State::GetInputDevices() const {
|
void UDPTouchFactory::EndConfiguration() {
|
||||||
// TODO support binding udp devices
|
polling = false;
|
||||||
return {};
|
client->EndConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::ReloadUDPClient() {
|
Common::ParamPackage UDPTouchFactory::GetNextInput() {
|
||||||
client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
|
Common::ParamPackage params;
|
||||||
Settings::values.udp_pad_index);
|
CemuhookUDP::UDPPadStatus pad;
|
||||||
|
auto& queue = client->GetPadQueue();
|
||||||
|
for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
|
||||||
|
while (queue[pad_number].Pop(pad)) {
|
||||||
|
if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
params.Set("engine", "cemuhookudp");
|
||||||
|
params.Set("ip", "127.0.0.1");
|
||||||
|
params.Set("port", 26760);
|
||||||
|
params.Set("pad_index", static_cast<int>(pad_number));
|
||||||
|
params.Set("touch", static_cast<u16>(pad.touch));
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<State> Init() {
|
} // namespace InputCommon
|
||||||
return std::make_unique<State>();
|
|
||||||
}
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
|
||||||
|
|
|
@ -1,32 +1,57 @@
|
||||||
// Copyright 2018 Citra Emulator Project
|
// Copyright 2020 yuzu Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include "core/frontend/input.h"
|
||||||
#include "common/param_package.h"
|
#include "input_common/udp/client.h"
|
||||||
|
|
||||||
namespace InputCommon::CemuhookUDP {
|
namespace InputCommon {
|
||||||
|
|
||||||
class Client;
|
/// A motion device factory that creates motion devices from udp clients
|
||||||
class UDPMotionFactory;
|
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
||||||
class UDPTouchFactory;
|
|
||||||
|
|
||||||
class State {
|
|
||||||
public:
|
public:
|
||||||
State();
|
explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
|
||||||
~State();
|
|
||||||
void ReloadUDPClient();
|
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
|
||||||
std::vector<Common::ParamPackage> GetInputDevices() const;
|
|
||||||
|
Common::ParamPackage GetNextInput();
|
||||||
|
|
||||||
|
/// For device input configuration/polling
|
||||||
|
void BeginConfiguration();
|
||||||
|
void EndConfiguration();
|
||||||
|
|
||||||
|
bool IsPolling() const {
|
||||||
|
return polling;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Client> client;
|
std::shared_ptr<CemuhookUDP::Client> client;
|
||||||
std::shared_ptr<UDPMotionFactory> motion_factory;
|
bool polling = false;
|
||||||
std::shared_ptr<UDPTouchFactory> touch_factory;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<State> Init();
|
/// 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_);
|
||||||
|
|
||||||
} // namespace InputCommon::CemuhookUDP
|
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
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "core/hle/service/sm/sm.h"
|
#include "core/hle/service/sm/sm.h"
|
||||||
#include "input_common/gcadapter/gc_poller.h"
|
#include "input_common/gcadapter/gc_poller.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
#include "input_common/udp/udp.h"
|
||||||
#include "ui_configure_input_player.h"
|
#include "ui_configure_input_player.h"
|
||||||
#include "yuzu/configuration/config.h"
|
#include "yuzu/configuration/config.h"
|
||||||
#include "yuzu/configuration/configure_input_player.h"
|
#include "yuzu/configuration/configure_input_player.h"
|
||||||
|
@ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
|
||||||
return GetKeyName(param.Get("code", 0));
|
return GetKeyName(param.Get("code", 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (param.Get("engine", "") == "cemuhookudp") {
|
||||||
|
if (param.Has("pad_index")) {
|
||||||
|
const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
|
||||||
|
return QObject::tr("Motion %1").arg(motion_str);
|
||||||
|
}
|
||||||
|
return GetKeyName(param.Get("code", 0));
|
||||||
|
}
|
||||||
|
|
||||||
if (param.Get("engine", "") == "sdl") {
|
if (param.Get("engine", "") == "sdl") {
|
||||||
if (param.Has("hat")) {
|
if (param.Has("hat")) {
|
||||||
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
|
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
|
||||||
|
@ -455,6 +464,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (input_subsystem->GetUDPMotions()->IsPolling()) {
|
||||||
|
params = input_subsystem->GetUDPMotions()->GetNextInput();
|
||||||
|
if (params.Has("engine")) {
|
||||||
|
SetPollingResult(params, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (auto& poller : device_pollers) {
|
for (auto& poller : device_pollers) {
|
||||||
params = poller->GetNextInput();
|
params = poller->GetNextInput();
|
||||||
if (params.Has("engine")) {
|
if (params.Has("engine")) {
|
||||||
|
@ -746,6 +762,10 @@ void ConfigureInputPlayer::HandleClick(
|
||||||
input_subsystem->GetGCAnalogs()->BeginConfiguration();
|
input_subsystem->GetGCAnalogs()->BeginConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == InputCommon::Polling::DeviceType::Motion) {
|
||||||
|
input_subsystem->GetUDPMotions()->BeginConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
timeout_timer->start(2500); // Cancel after 2.5 seconds
|
timeout_timer->start(2500); // Cancel after 2.5 seconds
|
||||||
poll_timer->start(50); // Check for new inputs every 50ms
|
poll_timer->start(50); // Check for new inputs every 50ms
|
||||||
}
|
}
|
||||||
|
@ -763,6 +783,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
|
||||||
input_subsystem->GetGCButtons()->EndConfiguration();
|
input_subsystem->GetGCButtons()->EndConfiguration();
|
||||||
input_subsystem->GetGCAnalogs()->EndConfiguration();
|
input_subsystem->GetGCAnalogs()->EndConfiguration();
|
||||||
|
|
||||||
|
input_subsystem->GetUDPMotions()->EndConfiguration();
|
||||||
|
|
||||||
if (!abort) {
|
if (!abort) {
|
||||||
(*input_setter)(params);
|
(*input_setter)(params);
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue