Merge pull request #4059 from zhaowenlan1779/udp-input-ui
citra_qt: add motion/touch config
This commit is contained in:
commit
50270fd791
|
@ -39,6 +39,8 @@ add_executable(citra-qt
|
|||
configuration/configure_graphics.h
|
||||
configuration/configure_input.cpp
|
||||
configuration/configure_input.h
|
||||
configuration/configure_motion_touch.cpp
|
||||
configuration/configure_motion_touch.h
|
||||
configuration/configure_system.cpp
|
||||
configuration/configure_system.h
|
||||
configuration/configure_web.cpp
|
||||
|
@ -115,6 +117,7 @@ set(UIS
|
|||
configuration/configure_general.ui
|
||||
configuration/configure_graphics.ui
|
||||
configuration/configure_input.ui
|
||||
configuration/configure_motion_touch.ui
|
||||
configuration/configure_system.ui
|
||||
configuration/configure_web.ui
|
||||
debugger/registers.ui
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <QTimer>
|
||||
#include "citra_qt/configuration/config.h"
|
||||
#include "citra_qt/configuration/configure_input.h"
|
||||
#include "citra_qt/configuration/configure_motion_touch.h"
|
||||
#include "common/param_package.h"
|
||||
|
||||
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(); });
|
||||
|
||||
timeout_timer->setSingleShot(true);
|
||||
|
|
|
@ -556,6 +556,34 @@
|
|||
</item>
|
||||
<item>
|
||||
<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>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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{};
|
||||
};
|
|
@ -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>
|
|
@ -55,6 +55,15 @@ public:
|
|||
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>
|
||||
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
|
||||
std::unique_lock<std::mutex> lk(mutex);
|
||||
|
|
|
@ -76,6 +76,11 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
|
|||
return circle_pad_param.Serialize();
|
||||
}
|
||||
|
||||
void ReloadInputDevices() {
|
||||
if (udp)
|
||||
udp->ReloadUDPClient();
|
||||
}
|
||||
|
||||
namespace Polling {
|
||||
|
||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
|
||||
|
|
|
@ -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,
|
||||
int key_modifier, float modifier_scale);
|
||||
|
||||
/// Reloads the input devices
|
||||
void ReloadInputDevices();
|
||||
|
||||
namespace Polling {
|
||||
|
||||
enum class DeviceType { Button, Analog };
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "input_common/udp/client.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,
|
||||
u8 pad_index, u32 client_id)
|
||||
: status(status) {
|
||||
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()};
|
||||
StartCommunication(host, port, pad_index, client_id);
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
|
@ -141,6 +135,12 @@ Client::~Client() {
|
|||
thread.join();
|
||||
}
|
||||
|
||||
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
||||
socket->Stop();
|
||||
thread.join();
|
||||
StartCommunication(host, port, pad_index, client_id);
|
||||
}
|
||||
|
||||
void Client::OnVersion(Response::Version data) {
|
||||
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
@ -47,15 +48,48 @@ public:
|
|||
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
|
||||
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
|
||||
~Client();
|
||||
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
|
||||
u32 client_id = 24872);
|
||||
|
||||
private:
|
||||
void OnVersion(Response::Version);
|
||||
void OnPortInfo(Response::PortInfo);
|
||||
void OnPadData(Response::PadData);
|
||||
void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
|
||||
|
||||
std::unique_ptr<Socket> socket;
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
std::thread thread;
|
||||
u64 packet_sequence = 0;
|
||||
};
|
||||
|
||||
/// An async job allowing configuration of the touchpad calibration.
|
||||
class CalibrationConfigurationJob {
|
||||
public:
|
||||
enum class Status {
|
||||
Initialized,
|
||||
Ready,
|
||||
Stage1Completed,
|
||||
Completed,
|
||||
};
|
||||
/**
|
||||
* Constructs and starts the job with the specified parameter.
|
||||
*
|
||||
* @param status_callback Callback for job status updates
|
||||
* @param data_callback Called when calibration data is ready
|
||||
*/
|
||||
explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
|
||||
u32 client_id, std::function<void(Status)> status_callback,
|
||||
std::function<void(u16, u16, u16, u16)> data_callback);
|
||||
~CalibrationConfigurationJob();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
Common::Event complete_event;
|
||||
};
|
||||
|
||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||
std::function<void()> success_callback,
|
||||
std::function<void()> failure_callback);
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
|
|
|
@ -85,6 +85,11 @@ State::~State() {
|
|||
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() {
|
||||
return std::make_unique<State>();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ class State {
|
|||
public:
|
||||
State();
|
||||
~State();
|
||||
void ReloadUDPClient();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Client> client;
|
||||
|
|
Reference in New Issue