Merge pull request #4059 from zhaowenlan1779/udp-input-ui

citra_qt: add motion/touch config
This commit is contained in:
James Rowe 2018-08-26 11:56:50 -06:00 committed by GitHub
commit 50270fd791
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 831 additions and 7 deletions

View File

@ -39,6 +39,8 @@ add_executable(citra-qt
configuration/configure_graphics.h configuration/configure_graphics.h
configuration/configure_input.cpp configuration/configure_input.cpp
configuration/configure_input.h configuration/configure_input.h
configuration/configure_motion_touch.cpp
configuration/configure_motion_touch.h
configuration/configure_system.cpp configuration/configure_system.cpp
configuration/configure_system.h configuration/configure_system.h
configuration/configure_web.cpp configuration/configure_web.cpp
@ -115,6 +117,7 @@ set(UIS
configuration/configure_general.ui configuration/configure_general.ui
configuration/configure_graphics.ui configuration/configure_graphics.ui
configuration/configure_input.ui configuration/configure_input.ui
configuration/configure_motion_touch.ui
configuration/configure_system.ui configuration/configure_system.ui
configuration/configure_web.ui configuration/configure_web.ui
debugger/registers.ui debugger/registers.ui

View File

@ -9,6 +9,7 @@
#include <QTimer> #include <QTimer>
#include "citra_qt/configuration/config.h" #include "citra_qt/configuration/config.h"
#include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_input.h"
#include "citra_qt/configuration/configure_motion_touch.h"
#include "common/param_package.h" #include "common/param_package.h"
const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
@ -159,6 +160,10 @@ ConfigureInput::ConfigureInput(QWidget* parent)
}); });
} }
connect(ui->buttonMotionTouch, &QPushButton::released, [this] {
QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
return motion_touch_dialog->exec();
});
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true); timeout_timer->setSingleShot(true);

View File

@ -556,6 +556,34 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="buttonMotionTouch">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Motion / Touch...</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -0,0 +1,276 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <QCloseEvent>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "citra_qt/configuration/configure_motion_touch.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "ui_configure_motion_touch.h"
CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
const std::string& host, u16 port,
u8 pad_index, u16 client_id)
: QDialog(parent) {
layout = new QVBoxLayout;
status_label = new QLabel(tr("Communicating with the server..."));
cancel_button = new QPushButton(tr("Cancel"));
connect(cancel_button, &QPushButton::clicked, this, [this] {
if (!completed)
job->Stop();
accept();
});
layout->addWidget(status_label);
layout->addWidget(cancel_button);
setLayout(layout);
using namespace InputCommon::CemuhookUDP;
job = std::move(std::make_unique<CalibrationConfigurationJob>(
host, port, pad_index, client_id,
[this](CalibrationConfigurationJob::Status status) {
QString text;
switch (status) {
case CalibrationConfigurationJob::Status::Ready:
text = tr("Touch the top left corner <br>of your touchpad.");
break;
case CalibrationConfigurationJob::Status::Stage1Completed:
text = tr("Now touch the bottom right corner <br>of your touchpad.");
break;
case CalibrationConfigurationJob::Status::Completed:
text = tr("Configuration completed!");
break;
}
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
if (status == CalibrationConfigurationJob::Status::Completed) {
QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
}
},
[this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
completed = true;
min_x = min_x_;
min_y = min_y_;
max_x = max_x_;
max_y = max_y_;
}));
}
CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
void CalibrationConfigurationDialog::UpdateLabelText(QString text) {
status_label->setText(text);
}
void CalibrationConfigurationDialog::UpdateButtonText(QString text) {
cancel_button->setText(text);
}
const std::array<std::pair<const char*, const char*>, 2> MotionProviders = {
{{"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}};
const std::array<std::pair<const char*, const char*>, 2> TouchProviders = {
{{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}}};
ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
ui->setupUi(this);
for (auto [provider, name] : MotionProviders) {
ui->motion_provider->addItem(tr(name), provider);
}
for (auto [provider, name] : TouchProviders) {
ui->touch_provider->addItem(tr(name), provider);
}
ui->udp_learn_more->setOpenExternalLinks(true);
ui->udp_learn_more->setText(
tr("<a "
"href='https://citra-emu.org/wiki/"
"using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
setConfiguration();
updateUiDisplay();
connectEvents();
}
ConfigureMotionTouch::~ConfigureMotionTouch() = default;
void ConfigureMotionTouch::setConfiguration() {
Common::ParamPackage motion_param(Settings::values.motion_device);
Common::ParamPackage touch_param(Settings::values.touch_device);
std::string motion_engine = motion_param.Get("engine", "motion_emu");
std::string touch_engine = touch_param.Get("engine", "emu_window");
ui->motion_provider->setCurrentIndex(
ui->motion_provider->findData(QString::fromStdString(motion_engine)));
ui->touch_provider->setCurrentIndex(
ui->touch_provider->findData(QString::fromStdString(touch_engine)));
ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
min_x = touch_param.Get("min_x", 100);
min_y = touch_param.Get("min_y", 50);
max_x = touch_param.Get("max_x", 1800);
max_y = touch_param.Get("max_y", 850);
ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
}
void ConfigureMotionTouch::updateUiDisplay() {
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
if (motion_engine == "motion_emu") {
ui->motion_sensitivity_label->setVisible(true);
ui->motion_sensitivity->setVisible(true);
} else {
ui->motion_sensitivity_label->setVisible(false);
ui->motion_sensitivity->setVisible(false);
}
if (touch_engine == "cemuhookudp") {
ui->touch_calibration->setVisible(true);
ui->touch_calibration_config->setVisible(true);
ui->touch_calibration_label->setVisible(true);
ui->touch_calibration->setText(QString("(%1, %2) - (%3, %4)")
.arg(QString::number(min_x), QString::number(min_y),
QString::number(max_x), QString::number(max_y)));
} else {
ui->touch_calibration->setVisible(false);
ui->touch_calibration_config->setVisible(false);
ui->touch_calibration_label->setVisible(false);
}
if (motion_engine == "cemuhookudp" || touch_engine == "cemuhookudp") {
ui->udp_config_group_box->setVisible(true);
} else {
ui->udp_config_group_box->setVisible(false);
}
}
void ConfigureMotionTouch::connectEvents() {
connect(ui->motion_provider,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[this](int index) { updateUiDisplay(); });
connect(ui->touch_provider,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[this](int index) { updateUiDisplay(); });
connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
connect(ui->touch_calibration_config, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchCalibration);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
if (CanCloseDialog())
reject();
});
}
void ConfigureMotionTouch::OnCemuhookUDPTest() {
ui->udp_test->setEnabled(false);
ui->udp_test->setText(tr("Testing"));
udp_test_in_progress = true;
InputCommon::CemuhookUDP::TestCommunication(
ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872,
[this] {
LOG_INFO(Frontend, "UDP input test success");
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
},
[this] {
LOG_ERROR(Frontend, "UDP input test failed");
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
});
}
void ConfigureMotionTouch::OnConfigureTouchCalibration() {
ui->touch_calibration_config->setEnabled(false);
ui->touch_calibration_config->setText(tr("Configuring"));
CalibrationConfigurationDialog* dialog = new CalibrationConfigurationDialog(
this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
dialog->exec();
if (dialog->completed) {
min_x = dialog->min_x;
min_y = dialog->min_y;
max_x = dialog->max_x;
max_y = dialog->max_y;
LOG_INFO(Frontend,
"UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
min_x, min_y, max_x, max_y);
updateUiDisplay();
} else {
LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
}
ui->touch_calibration_config->setEnabled(true);
ui->touch_calibration_config->setText(tr("Configure"));
}
void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
if (CanCloseDialog())
event->accept();
else
event->ignore();
}
void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
udp_test_in_progress = false;
if (result) {
QMessageBox::information(this, tr("Test Successful"),
tr("Successfully received data from the server."));
} else {
QMessageBox::warning(this, tr("Test Failed"),
tr("Could not receive valid data from the server.<br>Please verify "
"that the server is set up correctly and "
"the address and port are correct."));
}
ui->udp_test->setEnabled(true);
ui->udp_test->setText(tr("Test"));
}
bool ConfigureMotionTouch::CanCloseDialog() {
if (udp_test_in_progress) {
QMessageBox::warning(this, tr("Citra"),
tr("UDP Test or calibration configuration is in progress.<br>Please "
"wait for them to finish."));
return false;
}
return true;
}
void ConfigureMotionTouch::applyConfiguration() {
if (!CanCloseDialog())
return;
std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
Common::ParamPackage motion_param{}, touch_param{};
motion_param.Set("engine", motion_engine);
touch_param.Set("engine", touch_engine);
if (motion_engine == "motion_emu") {
motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
}
if (touch_engine == "cemuhookudp") {
touch_param.Set("min_x", min_x);
touch_param.Set("min_y", min_y);
touch_param.Set("max_x", max_x);
touch_param.Set("max_y", max_y);
}
Settings::values.motion_device = motion_param.Serialize();
Settings::values.touch_device = touch_param.Serialize();
Settings::values.udp_input_address = ui->udp_server->text().toStdString();
Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
InputCommon::ReloadInputDevices();
accept();
}

View File

@ -0,0 +1,72 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include "common/param_package.h"
#include "input_common/udp/udp.h"
class QVBoxLayout;
class QLabel;
class QPushButton;
namespace Ui {
class ConfigureMotionTouch;
}
/// A dialog for touchpad calibration configuration.
class CalibrationConfigurationDialog : public QDialog {
Q_OBJECT
public:
explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
u8 pad_index, u16 client_id);
~CalibrationConfigurationDialog();
private:
Q_INVOKABLE void UpdateLabelText(QString text);
Q_INVOKABLE void UpdateButtonText(QString text);
QVBoxLayout* layout;
QLabel* status_label;
QPushButton* cancel_button;
std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
// Configuration results
bool completed{};
u16 min_x, min_y, max_x, max_y;
friend class ConfigureMotionTouch;
};
class ConfigureMotionTouch : public QDialog {
Q_OBJECT
public:
explicit ConfigureMotionTouch(QWidget* parent = nullptr);
~ConfigureMotionTouch();
public slots:
void applyConfiguration();
private slots:
void OnCemuhookUDPTest();
void OnConfigureTouchCalibration();
private:
void closeEvent(QCloseEvent* event) override;
Q_INVOKABLE void ShowUDPTestResult(bool result);
void setConfiguration();
void updateUiDisplay();
void connectEvents();
bool CanCloseDialog();
std::unique_ptr<Ui::ConfigureMotionTouch> ui;
// Coordinate system of the CemuhookUDP touch provider
int min_x, min_y, max_x, max_y;
bool udp_test_in_progress{};
};

View File

@ -0,0 +1,294 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureMotionTouch</class>
<widget class="QDialog" name="ConfigureMotionTouch">
<property name="windowTitle">
<string>Configure Motion / Touch</string>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>450</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="motion_group_box">
<property name="title">
<string>Motion</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="motion_provider_label">
<property name="text">
<string>Motion Provider:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="motion_provider"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="motion_sensitivity_label">
<property name="text">
<string>Sensitivity:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="motion_sensitivity">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.010000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="touch_group_box">
<property name="title">
<string>Touch</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="touch_provider_label">
<property name="text">
<string>Touch Provider:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="touch_provider"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="touch_calibration_label">
<property name="text">
<string>Calibration:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="touch_calibration">
<property name="text">
<string>(100, 50) - (1800, 850)</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="touch_calibration_config">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="udp_config_group_box">
<property name="title">
<string>CemuhookUDP Config</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="udp_help">
<property name="text">
<string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="udp_server_label">
<property name="text">
<string>Server:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udp_server">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="udp_port_label">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udp_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="udp_pad_index_label">
<property name="text">
<string>Pad:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="udp_pad_index">
<item>
<property name="text">
<string>Pad 1</string>
</property>
</item>
<item>
<property name="text">
<string>Pad 2</string>
</property>
</item>
<item>
<property name="text">
<string>Pad 3</string>
</property>
</item>
<item>
<property name="text">
<string>Pad 4</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="udp_learn_more">
<property name="text">
<string>Learn More</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="udp_test">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>167</width>
<height>55</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureMotionTouch</receiver>
<slot>applyConfiguration()</slot>
<hints>
<hint type="sourcelabel">
<x>220</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>220</x>
<y>200</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -55,6 +55,15 @@ public:
is_set = false; is_set = false;
} }
template <class Duration>
bool WaitFor(const std::chrono::duration<Duration>& time) {
std::unique_lock<std::mutex> lk(mutex);
if (!condvar.wait_for(lk, time, [this] { return is_set; }))
return false;
is_set = false;
return true;
}
template <class Clock, class Duration> template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
std::unique_lock<std::mutex> lk(mutex); std::unique_lock<std::mutex> lk(mutex);

View File

@ -76,6 +76,11 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
return circle_pad_param.Serialize(); return circle_pad_param.Serialize();
} }
void ReloadInputDevices() {
if (udp)
udp->ReloadUDPClient();
}
namespace Polling { namespace Polling {
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {

View File

@ -37,6 +37,9 @@ std::string GenerateKeyboardParam(int key_code);
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
int key_modifier, float modifier_scale); int key_modifier, float modifier_scale);
/// Reloads the input devices
void ReloadInputDevices();
namespace Polling { namespace Polling {
enum class DeviceType { Button, Analog }; enum class DeviceType { Button, Analog };

View File

@ -11,7 +11,6 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/vector_math.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"
@ -128,12 +127,7 @@ static void SocketLoop(Socket* socket) {
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
u8 pad_index, u32 client_id) u8 pad_index, u32 client_id)
: status(status) { : status(status) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, StartCommunication(host, port, pad_index, client_id);
[this](Response::PortInfo info) { OnPortInfo(info); },
[this](Response::PadData data) { OnPadData(data); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
thread = std::thread{SocketLoop, this->socket.get()};
} }
Client::~Client() { Client::~Client() {
@ -141,6 +135,12 @@ Client::~Client() {
thread.join(); thread.join();
} }
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
socket->Stop();
thread.join();
StartCommunication(host, port, pad_index, client_id);
}
void Client::OnVersion(Response::Version data) { void Client::OnVersion(Response::Version data) {
LOG_TRACE(Input, "Version packet received: {}", data.version); LOG_TRACE(Input, "Version packet received: {}", data.version);
} }
@ -192,4 +192,93 @@ void Client::OnPadData(Response::PadData data) {
} }
} }
void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this](Response::PadData data) { OnPadData(data); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
thread = std::thread{SocketLoop, this->socket.get()};
}
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void()> success_callback,
std::function<void()> failure_callback) {
std::thread([=] {
Common::Event success_event;
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
[&](Response::PadData data) { success_event.Set(); }};
Socket socket{host, port, pad_index, client_id, callback};
std::thread worker_thread{SocketLoop, &socket};
bool result = success_event.WaitFor(std::chrono::seconds(8));
socket.Stop();
worker_thread.join();
if (result)
success_callback();
else
failure_callback();
})
.detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback) {
std::thread([=] {
constexpr u16 CALIBRATION_THRESHOLD = 100;
u16 min_x{UINT16_MAX}, min_y{UINT16_MAX};
u16 max_x, max_y;
Status current_status{Status::Initialized};
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
[&](Response::PadData data) {
if (current_status == Status::Initialized) {
// Receiving data means the communication is ready now
current_status = Status::Ready;
status_callback(current_status);
}
if (!data.touch_1.is_active)
return;
LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
data.touch_1.y);
min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
if (current_status == Status::Ready) {
// First touch - min data (min_x/min_y)
current_status = Status::Stage1Completed;
status_callback(current_status);
}
if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
// Set the current position as max value and finishes
// configuration
max_x = data.touch_1.x;
max_y = data.touch_1.y;
current_status = Status::Completed;
data_callback(min_x, min_y, max_x, max_y);
status_callback(current_status);
complete_event.Set();
}
}};
Socket socket{host, port, pad_index, client_id, 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 } // namespace InputCommon::CemuhookUDP

View File

@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/thread.h"
#include "common/vector_math.h" #include "common/vector_math.h"
namespace InputCommon::CemuhookUDP { namespace InputCommon::CemuhookUDP {
@ -47,15 +48,48 @@ public:
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
~Client(); ~Client();
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
u32 client_id = 24872);
private: private:
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);
std::unique_ptr<Socket> socket; std::unique_ptr<Socket> socket;
std::shared_ptr<DeviceStatus> status; std::shared_ptr<DeviceStatus> status;
std::thread thread; std::thread thread;
u64 packet_sequence = 0; u64 packet_sequence = 0;
}; };
/// An async job allowing configuration of the touchpad calibration.
class CalibrationConfigurationJob {
public:
enum class Status {
Initialized,
Ready,
Stage1Completed,
Completed,
};
/**
* Constructs and starts the job with the specified parameter.
*
* @param status_callback Callback for job status updates
* @param data_callback Called when calibration data is ready
*/
explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
u32 client_id, std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback);
~CalibrationConfigurationJob();
void Stop();
private:
Common::Event complete_event;
};
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void()> success_callback,
std::function<void()> failure_callback);
} // namespace InputCommon::CemuhookUDP } // namespace InputCommon::CemuhookUDP

View File

@ -85,6 +85,11 @@ State::~State() {
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
} }
void State::ReloadUDPClient() {
client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
Settings::values.udp_pad_index);
}
std::unique_ptr<State> Init() { std::unique_ptr<State> Init() {
return std::make_unique<State>(); return std::make_unique<State>();
} }

View File

@ -16,6 +16,7 @@ class State {
public: public:
State(); State();
~State(); ~State();
void ReloadUDPClient();
private: private:
std::unique_ptr<Client> client; std::unique_ptr<Client> client;