mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-01-08 17:50:31 +01:00
This fixes the slow motion and rubber banding in the pad settings dialog while using HID handlers. Previously everything was running on the main thread including the UI updates. This meant that the poll rate was simply too slow and we were never up to date with the current data.
1872 lines
59 KiB
C++
1872 lines
59 KiB
C++
#include <QCheckBox>
|
|
#include <QPushButton>
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#include <QSvgRenderer>
|
|
|
|
#include "qt_utils.h"
|
|
#include "pad_settings_dialog.h"
|
|
#include "pad_led_settings_dialog.h"
|
|
#include "ui_pad_settings_dialog.h"
|
|
#include "tooltips.h"
|
|
#include "gui_settings.h"
|
|
|
|
#include "Emu/System.h"
|
|
#include "Emu/system_utils.hpp"
|
|
#include "Emu/Io/Null/NullPadHandler.h"
|
|
#include "Utilities/File.h"
|
|
|
|
#include "Input/pad_thread.h"
|
|
#include "Input/product_info.h"
|
|
#include "Input/keyboard_pad_handler.h"
|
|
|
|
#include <thread>
|
|
|
|
LOG_CHANNEL(cfg_log, "CFG");
|
|
|
|
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
|
|
constexpr auto qstr = QString::fromStdString;
|
|
|
|
cfg_profile g_cfg_profile;
|
|
|
|
inline bool CreateConfigFile(const QString& dir, const QString& name)
|
|
{
|
|
if (!QDir().mkpath(dir))
|
|
{
|
|
cfg_log.fatal("Failed to create dir %s", sstr(dir));
|
|
return false;
|
|
}
|
|
|
|
const QString filename = dir + name + ".yml";
|
|
QFile new_file(filename);
|
|
|
|
if (!new_file.open(QIODevice::WriteOnly))
|
|
{
|
|
cfg_log.fatal("Failed to create file %s", sstr(filename));
|
|
return false;
|
|
}
|
|
|
|
new_file.close();
|
|
return true;
|
|
}
|
|
|
|
pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_settings, QWidget *parent, const GameInfo *game)
|
|
: QDialog(parent)
|
|
, ui(new Ui::pad_settings_dialog)
|
|
, m_gui_settings(std::move(gui_settings))
|
|
{
|
|
pad::set_enabled(false);
|
|
|
|
ui->setupUi(this);
|
|
|
|
if (game)
|
|
{
|
|
m_title_id = game->serial;
|
|
setWindowTitle(tr("Gamepad Settings: [%0] %1").arg(qstr(game->serial)).arg(qstr(game->name).simplified()));
|
|
}
|
|
else
|
|
{
|
|
setWindowTitle(tr("Gamepad Settings"));
|
|
}
|
|
|
|
// Load profiles
|
|
g_cfg_profile.load();
|
|
|
|
if (m_title_id.empty())
|
|
{
|
|
const QString profile_dir = qstr(rpcs3::utils::get_input_config_dir(m_title_id));
|
|
QStringList profiles = gui::utils::get_dir_entries(QDir(profile_dir), QStringList() << "*.yml");
|
|
QString active_profile = qstr(g_cfg_profile.active_profiles.get_value(g_cfg_profile.global_key));
|
|
|
|
if (!profiles.contains(active_profile))
|
|
{
|
|
const QString default_profile = qstr(g_cfg_profile.default_profile);
|
|
|
|
if (!profiles.contains(default_profile) && CreateConfigFile(profile_dir, default_profile))
|
|
{
|
|
profiles.prepend(default_profile);
|
|
}
|
|
|
|
active_profile = default_profile;
|
|
}
|
|
|
|
for (const QString& profile : profiles)
|
|
{
|
|
ui->chooseProfile->addItem(profile);
|
|
}
|
|
|
|
ui->chooseProfile->setCurrentText(active_profile);
|
|
}
|
|
else
|
|
{
|
|
ui->chooseProfile->addItem(qstr(m_title_id));
|
|
ui->gb_profiles->setEnabled(false);
|
|
}
|
|
|
|
// Create tab widget for 7 players
|
|
for (int i = 1; i < 8; i++)
|
|
{
|
|
const QString tab_title = tr("Player %0").arg(i);
|
|
|
|
if (i == 1)
|
|
{
|
|
ui->tabWidget->setTabText(0, tab_title);
|
|
}
|
|
else
|
|
{
|
|
ui->tabWidget->addTab(new QWidget, tab_title);
|
|
}
|
|
}
|
|
|
|
// On tab change: move the layout to the new tab and refresh
|
|
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &pad_settings_dialog::OnTabChanged);
|
|
|
|
// Combobox: Input type
|
|
connect(ui->chooseHandler, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeHandler);
|
|
|
|
// Combobox: Devices
|
|
connect(ui->chooseDevice, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &pad_settings_dialog::ChangeDevice);
|
|
|
|
// Combobox: Profiles
|
|
connect(ui->chooseProfile, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeProfile);
|
|
|
|
// Pushbutton: Add Profile
|
|
connect(ui->b_addProfile, &QAbstractButton::clicked, this, &pad_settings_dialog::AddProfile);
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Filter Noise"));
|
|
|
|
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button)
|
|
{
|
|
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
|
{
|
|
SaveExit();
|
|
}
|
|
else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel))
|
|
{
|
|
CancelExit();
|
|
}
|
|
else if (button == ui->buttonBox->button(QDialogButtonBox::Reset))
|
|
{
|
|
OnPadButtonClicked(button_ids::id_blacklist);
|
|
}
|
|
else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults))
|
|
{
|
|
OnPadButtonClicked(button_ids::id_reset_parameters);
|
|
}
|
|
});
|
|
|
|
// Refresh Button
|
|
connect(ui->b_refresh, &QPushButton::clicked, this, &pad_settings_dialog::RefreshHandlers);
|
|
|
|
ui->chooseClass->addItem(tr("Standard (Pad)")); // CELL_PAD_PCLASS_TYPE_STANDARD = 0x00,
|
|
ui->chooseClass->addItem(tr("Guitar")); // CELL_PAD_PCLASS_TYPE_GUITAR = 0x01,
|
|
ui->chooseClass->addItem(tr("Drum")); // CELL_PAD_PCLASS_TYPE_DRUM = 0x02,
|
|
ui->chooseClass->addItem(tr("DJ")); // CELL_PAD_PCLASS_TYPE_DJ = 0x03,
|
|
ui->chooseClass->addItem(tr("Dance Mat")); // CELL_PAD_PCLASS_TYPE_DANCEMAT = 0x04,
|
|
ui->chooseClass->addItem(tr("Navigation")); // CELL_PAD_PCLASS_TYPE_NAVIGATION = 0x05,
|
|
|
|
connect(ui->chooseClass, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &pad_settings_dialog::HandleDeviceClassChange);
|
|
|
|
ui->chb_show_emulated_values->setChecked(m_gui_settings->GetValue(gui::pads_show_emulated).toBool());
|
|
|
|
connect(ui->chb_show_emulated_values, &QCheckBox::clicked, [this](bool checked)
|
|
{
|
|
m_gui_settings->SetValue(gui::pads_show_emulated, checked);
|
|
const auto& cfg = GetPlayerConfig();
|
|
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, cfg.lpadsquircling, cfg.lstickmultiplier / 100.0);
|
|
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, cfg.rpadsquircling, cfg.rstickmultiplier / 100.0);
|
|
});
|
|
|
|
ui->mouse_movement->addItem(tr("Relative"), static_cast<int>(mouse_movement_mode::relative));
|
|
ui->mouse_movement->addItem(tr("Absolute"), static_cast<int>(mouse_movement_mode::absolute));
|
|
|
|
// Initialize configurable buttons
|
|
InitButtons();
|
|
|
|
// Initialize tooltips
|
|
SubscribeTooltips();
|
|
|
|
// Repaint controller image
|
|
QSvgRenderer renderer(QStringLiteral(":/Icons/DualShock_3.svg"));
|
|
QPixmap controller_pixmap(renderer.defaultSize() * 10);
|
|
controller_pixmap.fill(Qt::transparent);
|
|
QPainter painter(&controller_pixmap);
|
|
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
|
renderer.render(&painter, controller_pixmap.rect());
|
|
ui->l_controller->setPixmap(gui::utils::get_colorized_pixmap(controller_pixmap, QColor(), gui::utils::get_label_color("l_controller"), false, true));
|
|
|
|
// Show default widgets first in order to calculate the required size for the scroll area (see pad_settings_dialog::ResizeDialog)
|
|
ui->left_stack->setCurrentIndex(0);
|
|
ui->right_stack->setCurrentIndex(0);
|
|
|
|
// Set up first tab
|
|
OnTabChanged(0);
|
|
ChangeProfile(ui->chooseProfile->currentText());
|
|
}
|
|
|
|
pad_settings_dialog::~pad_settings_dialog()
|
|
{
|
|
if (m_input_thread)
|
|
{
|
|
m_input_thread_state = input_thread_state::pausing;
|
|
auto& thread = *m_input_thread;
|
|
thread = thread_state::aborting;
|
|
thread();
|
|
}
|
|
|
|
m_gui_settings->SetValue(gui::pads_geometry, saveGeometry());
|
|
|
|
if (!Emu.IsStopped())
|
|
{
|
|
pad::reset(Emu.GetTitleID());
|
|
}
|
|
|
|
pad::set_enabled(true);
|
|
}
|
|
|
|
void pad_settings_dialog::showEvent(QShowEvent* event)
|
|
{
|
|
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), 0, 0, 0, 0);
|
|
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), 0, 0, 0, 0);
|
|
|
|
// Resize in order to fit into our scroll area
|
|
if (!restoreGeometry(m_gui_settings->GetValue(gui::pads_geometry).toByteArray()))
|
|
{
|
|
ResizeDialog();
|
|
}
|
|
|
|
QDialog::showEvent(event);
|
|
}
|
|
|
|
void pad_settings_dialog::InitButtons()
|
|
{
|
|
m_pad_buttons = new QButtonGroup(this);
|
|
m_palette = ui->b_left->palette(); // save normal palette
|
|
|
|
const auto insert_button = [this](int id, QPushButton* button)
|
|
{
|
|
m_pad_buttons->addButton(button, id);
|
|
button->installEventFilter(this);
|
|
};
|
|
|
|
insert_button(button_ids::id_pad_lstick_left, ui->b_lstick_left);
|
|
insert_button(button_ids::id_pad_lstick_down, ui->b_lstick_down);
|
|
insert_button(button_ids::id_pad_lstick_right, ui->b_lstick_right);
|
|
insert_button(button_ids::id_pad_lstick_up, ui->b_lstick_up);
|
|
|
|
insert_button(button_ids::id_pad_left, ui->b_left);
|
|
insert_button(button_ids::id_pad_down, ui->b_down);
|
|
insert_button(button_ids::id_pad_right, ui->b_right);
|
|
insert_button(button_ids::id_pad_up, ui->b_up);
|
|
|
|
insert_button(button_ids::id_pad_l1, ui->b_shift_l1);
|
|
insert_button(button_ids::id_pad_l2, ui->b_shift_l2);
|
|
insert_button(button_ids::id_pad_l3, ui->b_shift_l3);
|
|
|
|
insert_button(button_ids::id_pad_start, ui->b_start);
|
|
insert_button(button_ids::id_pad_select, ui->b_select);
|
|
insert_button(button_ids::id_pad_ps, ui->b_ps);
|
|
|
|
insert_button(button_ids::id_pad_r1, ui->b_shift_r1);
|
|
insert_button(button_ids::id_pad_r2, ui->b_shift_r2);
|
|
insert_button(button_ids::id_pad_r3, ui->b_shift_r3);
|
|
|
|
insert_button(button_ids::id_pad_square, ui->b_square);
|
|
insert_button(button_ids::id_pad_cross, ui->b_cross);
|
|
insert_button(button_ids::id_pad_circle, ui->b_circle);
|
|
insert_button(button_ids::id_pad_triangle, ui->b_triangle);
|
|
|
|
insert_button(button_ids::id_pad_rstick_left, ui->b_rstick_left);
|
|
insert_button(button_ids::id_pad_rstick_down, ui->b_rstick_down);
|
|
insert_button(button_ids::id_pad_rstick_right, ui->b_rstick_right);
|
|
insert_button(button_ids::id_pad_rstick_up, ui->b_rstick_up);
|
|
|
|
insert_button(button_ids::id_pressure_intensity, ui->b_pressure_intensity);
|
|
|
|
m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh);
|
|
m_pad_buttons->addButton(ui->b_addProfile, button_ids::id_add_profile);
|
|
|
|
connect(m_pad_buttons, &QButtonGroup::idClicked, this, &pad_settings_dialog::OnPadButtonClicked);
|
|
|
|
connect(&m_timer, &QTimer::timeout, this, [this]()
|
|
{
|
|
if (--m_seconds <= 0)
|
|
{
|
|
ReactivateButtons();
|
|
return;
|
|
}
|
|
m_pad_buttons->button(m_button_id)->setText(tr("[ Waiting %1 ]").arg(m_seconds));
|
|
});
|
|
|
|
connect(ui->chb_vibration_large, &QCheckBox::clicked, this, [this](bool checked)
|
|
{
|
|
if (!checked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ui->chb_vibration_switch->isChecked() ? SetPadData(m_min_force, m_max_force)
|
|
: SetPadData(m_max_force, m_min_force);
|
|
|
|
QTimer::singleShot(300, [this]()
|
|
{
|
|
SetPadData(m_min_force, m_min_force);
|
|
});
|
|
});
|
|
|
|
connect(ui->chb_vibration_small, &QCheckBox::clicked, this, [this](bool checked)
|
|
{
|
|
if (!checked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ui->chb_vibration_switch->isChecked() ? SetPadData(m_max_force, m_min_force)
|
|
: SetPadData(m_min_force, m_max_force);
|
|
|
|
QTimer::singleShot(300, [this]()
|
|
{
|
|
SetPadData(m_min_force, m_min_force);
|
|
});
|
|
});
|
|
|
|
connect(ui->chb_vibration_switch, &QCheckBox::clicked, this, [this](bool checked)
|
|
{
|
|
checked ? SetPadData(m_min_force, m_max_force)
|
|
: SetPadData(m_max_force, m_min_force);
|
|
|
|
QTimer::singleShot(200, [this, checked]()
|
|
{
|
|
checked ? SetPadData(m_max_force, m_min_force)
|
|
: SetPadData(m_min_force, m_max_force);
|
|
|
|
QTimer::singleShot(200, [this]()
|
|
{
|
|
SetPadData(m_min_force, m_min_force);
|
|
});
|
|
});
|
|
});
|
|
|
|
connect(ui->slider_stick_left, &QSlider::valueChanged, this, [&](int value)
|
|
{
|
|
RepaintPreviewLabel(ui->preview_stick_left, value, ui->slider_stick_left->size().width(), m_lx, m_ly, ui->squircle_left->value(), ui->stick_multi_left->value());
|
|
});
|
|
|
|
connect(ui->slider_stick_right, &QSlider::valueChanged, this, [&](int value)
|
|
{
|
|
RepaintPreviewLabel(ui->preview_stick_right, value, ui->slider_stick_right->size().width(), m_rx, m_ry, ui->squircle_right->value(), ui->stick_multi_right->value());
|
|
});
|
|
|
|
// Open LED settings
|
|
connect(ui->b_led_settings, &QPushButton::clicked, this, [this]()
|
|
{
|
|
// Allow LED battery indication while the dialog is open
|
|
ensure(m_handler);
|
|
const auto& cfg = GetPlayerConfig();
|
|
SetPadData(0, 0, cfg.led_battery_indicator.get());
|
|
pad_led_settings_dialog dialog(this, cfg.colorR, cfg.colorG, cfg.colorB, m_handler->has_rgb(), m_handler->has_battery(), cfg.led_low_battery_blink.get(), cfg.led_battery_indicator.get(), cfg.led_battery_indicator_brightness);
|
|
connect(&dialog, &pad_led_settings_dialog::pass_led_settings, this, &pad_settings_dialog::apply_led_settings);
|
|
dialog.exec();
|
|
SetPadData(0, 0);
|
|
});
|
|
|
|
// Enable Button Remapping
|
|
const auto callback = [this](u16 val, std::string name, std::string pad_name, u32 battery_level, pad_preview_values preview_values)
|
|
{
|
|
SwitchPadInfo(pad_name, true);
|
|
|
|
if (!m_enable_buttons && !m_timer.isActive())
|
|
{
|
|
SwitchButtons(true);
|
|
}
|
|
|
|
if (m_handler->has_deadzones())
|
|
{
|
|
ui->preview_trigger_left->setValue(preview_values[0]);
|
|
ui->preview_trigger_right->setValue(preview_values[1]);
|
|
|
|
if (m_lx != preview_values[2] || m_ly != preview_values[3])
|
|
{
|
|
m_lx = preview_values[2];
|
|
m_ly = preview_values[3];
|
|
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, ui->squircle_left->value(), ui->stick_multi_left->value());
|
|
}
|
|
if (m_rx != preview_values[4] || m_ry != preview_values[5])
|
|
{
|
|
m_rx = preview_values[4];
|
|
m_ry = preview_values[5];
|
|
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, ui->squircle_right->value(), ui->stick_multi_right->value());
|
|
}
|
|
}
|
|
|
|
ui->pb_battery->setValue(m_enable_battery ? battery_level : 0);
|
|
|
|
if (val <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cfg_log.notice("get_next_button_press: %s device %s button %s pressed with value %d", m_handler->m_type, pad_name, name, val);
|
|
|
|
if (m_button_id > button_ids::id_pad_begin && m_button_id < button_ids::id_pad_end)
|
|
{
|
|
m_cfg_entries[m_button_id].key = name;
|
|
m_cfg_entries[m_button_id].text = qstr(name);
|
|
ReactivateButtons();
|
|
}
|
|
};
|
|
|
|
// Disable Button Remapping
|
|
const auto fail_callback = [this](const std::string& pad_name)
|
|
{
|
|
SwitchPadInfo(pad_name, false);
|
|
|
|
if (m_enable_buttons)
|
|
{
|
|
SwitchButtons(false);
|
|
}
|
|
|
|
ui->pb_battery->setValue(0);
|
|
|
|
if (m_handler->has_deadzones())
|
|
{
|
|
ui->preview_trigger_left->setValue(0);
|
|
ui->preview_trigger_right->setValue(0);
|
|
|
|
if (m_lx != 0 || m_ly != 0)
|
|
{
|
|
m_lx = 0;
|
|
m_ly = 0;
|
|
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, ui->squircle_left->value(), ui->stick_multi_left->value());
|
|
}
|
|
if (m_rx != 0 || m_ry != 0)
|
|
{
|
|
m_rx = 0;
|
|
m_ry = 0;
|
|
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, ui->squircle_right->value(), ui->stick_multi_right->value());
|
|
}
|
|
}
|
|
};
|
|
|
|
// Use timer to display button input
|
|
connect(&m_timer_input, &QTimer::timeout, this, [this, callback, fail_callback]()
|
|
{
|
|
input_callback_data data;
|
|
{
|
|
std::lock_guard lock(m_input_mutex);
|
|
data = m_input_callback_data;
|
|
m_input_callback_data.has_new_data = false;
|
|
}
|
|
|
|
if (data.has_new_data)
|
|
{
|
|
if (data.success)
|
|
{
|
|
callback(data.val, std::move(data.name), std::move(data.pad_name), data.battery_level, std::move(data.preview_values));
|
|
}
|
|
else
|
|
{
|
|
fail_callback(data.pad_name);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Use timer to refresh pad connection status
|
|
connect(&m_timer_pad_refresh, &QTimer::timeout, this, [this]()
|
|
{
|
|
for (int i = 0; i < ui->chooseDevice->count(); i++)
|
|
{
|
|
if (!ui->chooseDevice->itemData(i).canConvert<pad_device_info>())
|
|
{
|
|
cfg_log.fatal("Cannot convert itemData for index %d and itemText %s", i, sstr(ui->chooseDevice->itemText(i)));
|
|
continue;
|
|
}
|
|
|
|
const pad_device_info info = ui->chooseDevice->itemData(i).value<pad_device_info>();
|
|
|
|
std::lock_guard lock(m_handler_mutex);
|
|
|
|
m_handler->get_next_button_press(info.name,
|
|
[this](u16, std::string, std::string pad_name, u32, pad_preview_values) { SwitchPadInfo(pad_name, true); },
|
|
[this](std::string pad_name) { SwitchPadInfo(pad_name, false); }, false);
|
|
}
|
|
});
|
|
|
|
// Use thread to get button input
|
|
m_input_thread = std::make_unique<named_thread<std::function<void()>>>("Pad Settings Thread", [this]()
|
|
{
|
|
while (thread_ctrl::state() != thread_state::aborting)
|
|
{
|
|
thread_ctrl::wait_for(1000);
|
|
|
|
if (m_input_thread_state != input_thread_state::active)
|
|
{
|
|
if (m_input_thread_state == input_thread_state::pausing)
|
|
{
|
|
m_input_thread_state = input_thread_state::paused;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
std::lock_guard lock(m_handler_mutex);
|
|
|
|
const std::vector<std::string> buttons =
|
|
{
|
|
m_cfg_entries[button_ids::id_pad_l2].key, m_cfg_entries[button_ids::id_pad_r2].key, m_cfg_entries[button_ids::id_pad_lstick_left].key,
|
|
m_cfg_entries[button_ids::id_pad_lstick_right].key, m_cfg_entries[button_ids::id_pad_lstick_down].key, m_cfg_entries[button_ids::id_pad_lstick_up].key,
|
|
m_cfg_entries[button_ids::id_pad_rstick_left].key, m_cfg_entries[button_ids::id_pad_rstick_right].key, m_cfg_entries[button_ids::id_pad_rstick_down].key,
|
|
m_cfg_entries[button_ids::id_pad_rstick_up].key
|
|
};
|
|
m_handler->get_next_button_press(m_device_name,
|
|
[this](u16 val, std::string name, std::string pad_name, u32 battery_level, pad_preview_values preview_values)
|
|
{
|
|
std::lock_guard lock(m_input_mutex);
|
|
m_input_callback_data.val = val;
|
|
m_input_callback_data.name = std::move(name);
|
|
m_input_callback_data.pad_name = std::move(pad_name);
|
|
m_input_callback_data.battery_level = battery_level;
|
|
m_input_callback_data.preview_values = std::move(preview_values);
|
|
m_input_callback_data.has_new_data = true;
|
|
m_input_callback_data.success = true;
|
|
},
|
|
[this](std::string pad_name)
|
|
{
|
|
std::lock_guard lock(m_input_mutex);
|
|
m_input_callback_data.pad_name = std::move(pad_name);
|
|
m_input_callback_data.has_new_data = true;
|
|
m_input_callback_data.success = false;
|
|
},
|
|
false, buttons);
|
|
}
|
|
});
|
|
}
|
|
|
|
void pad_settings_dialog::SetPadData(u32 large_motor, u32 small_motor, bool led_battery_indicator)
|
|
{
|
|
ensure(m_handler);
|
|
const auto& cfg = GetPlayerConfig();
|
|
|
|
std::lock_guard lock(m_handler_mutex);
|
|
m_handler->SetPadData(m_device_name, GetPlayerIndex(), large_motor, small_motor, cfg.colorR, cfg.colorG, cfg.colorB, led_battery_indicator, cfg.led_battery_indicator_brightness);
|
|
}
|
|
|
|
// Slot to handle the data from a signal in the led settings dialog
|
|
void pad_settings_dialog::apply_led_settings(int colorR, int colorG, int colorB, bool led_low_battery_blink, bool led_battery_indicator, int led_battery_indicator_brightness)
|
|
{
|
|
ensure(m_handler);
|
|
auto& cfg = GetPlayerConfig();
|
|
cfg.colorR.set(colorR);
|
|
cfg.colorG.set(colorG);
|
|
cfg.colorB.set(colorB);
|
|
cfg.led_battery_indicator.set(led_battery_indicator);
|
|
cfg.led_battery_indicator_brightness.set(led_battery_indicator_brightness);
|
|
cfg.led_low_battery_blink.set(led_low_battery_blink);
|
|
SetPadData(0, 0, led_battery_indicator);
|
|
}
|
|
|
|
void pad_settings_dialog::SwitchPadInfo(const std::string& pad_name, bool is_connected)
|
|
{
|
|
for (int i = 0; i < ui->chooseDevice->count(); i++)
|
|
{
|
|
const pad_device_info info = ui->chooseDevice->itemData(i).value<pad_device_info>();
|
|
if (info.name == pad_name)
|
|
{
|
|
if (info.is_connected != is_connected)
|
|
{
|
|
ui->chooseDevice->setItemData(i, QVariant::fromValue(pad_device_info{ pad_name, is_connected }));
|
|
ui->chooseDevice->setItemText(i, is_connected ? qstr(pad_name) : (qstr(pad_name) + Disconnected_suffix));
|
|
}
|
|
|
|
if (!is_connected && m_timer.isActive() && ui->chooseDevice->currentIndex() == i)
|
|
{
|
|
ReactivateButtons();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::ReloadButtons()
|
|
{
|
|
m_cfg_entries.clear();
|
|
|
|
auto updateButton = [this](int id, QPushButton* button, cfg::string* cfg_text)
|
|
{
|
|
const QString text = qstr(*cfg_text);
|
|
m_cfg_entries.insert(std::make_pair(id, pad_button{cfg_text, *cfg_text, text}));
|
|
button->setText(text);
|
|
};
|
|
|
|
auto& cfg = GetPlayerConfig();
|
|
|
|
updateButton(button_ids::id_pad_lstick_left, ui->b_lstick_left, &cfg.ls_left);
|
|
updateButton(button_ids::id_pad_lstick_down, ui->b_lstick_down, &cfg.ls_down);
|
|
updateButton(button_ids::id_pad_lstick_right, ui->b_lstick_right, &cfg.ls_right);
|
|
updateButton(button_ids::id_pad_lstick_up, ui->b_lstick_up, &cfg.ls_up);
|
|
|
|
updateButton(button_ids::id_pad_left, ui->b_left, &cfg.left);
|
|
updateButton(button_ids::id_pad_down, ui->b_down, &cfg.down);
|
|
updateButton(button_ids::id_pad_right, ui->b_right, &cfg.right);
|
|
updateButton(button_ids::id_pad_up, ui->b_up, &cfg.up);
|
|
|
|
updateButton(button_ids::id_pad_l1, ui->b_shift_l1, &cfg.l1);
|
|
updateButton(button_ids::id_pad_l2, ui->b_shift_l2, &cfg.l2);
|
|
updateButton(button_ids::id_pad_l3, ui->b_shift_l3, &cfg.l3);
|
|
|
|
updateButton(button_ids::id_pad_start, ui->b_start, &cfg.start);
|
|
updateButton(button_ids::id_pad_select, ui->b_select, &cfg.select);
|
|
updateButton(button_ids::id_pad_ps, ui->b_ps, &cfg.ps);
|
|
|
|
updateButton(button_ids::id_pad_r1, ui->b_shift_r1, &cfg.r1);
|
|
updateButton(button_ids::id_pad_r2, ui->b_shift_r2, &cfg.r2);
|
|
updateButton(button_ids::id_pad_r3, ui->b_shift_r3, &cfg.r3);
|
|
|
|
updateButton(button_ids::id_pad_square, ui->b_square, &cfg.square);
|
|
updateButton(button_ids::id_pad_cross, ui->b_cross, &cfg.cross);
|
|
updateButton(button_ids::id_pad_circle, ui->b_circle, &cfg.circle);
|
|
updateButton(button_ids::id_pad_triangle, ui->b_triangle, &cfg.triangle);
|
|
|
|
updateButton(button_ids::id_pad_rstick_left, ui->b_rstick_left, &cfg.rs_left);
|
|
updateButton(button_ids::id_pad_rstick_down, ui->b_rstick_down, &cfg.rs_down);
|
|
updateButton(button_ids::id_pad_rstick_right, ui->b_rstick_right, &cfg.rs_right);
|
|
updateButton(button_ids::id_pad_rstick_up, ui->b_rstick_up, &cfg.rs_up);
|
|
|
|
updateButton(button_ids::id_pressure_intensity, ui->b_pressure_intensity, &cfg.pressure_intensity_button);
|
|
|
|
UpdateLabels(true);
|
|
}
|
|
|
|
void pad_settings_dialog::ReactivateButtons()
|
|
{
|
|
m_timer.stop();
|
|
m_seconds = MAX_SECONDS;
|
|
|
|
if (m_button_id == button_ids::id_pad_begin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_pad_buttons->button(m_button_id))
|
|
{
|
|
m_pad_buttons->button(m_button_id)->setPalette(m_palette);
|
|
m_pad_buttons->button(m_button_id)->releaseMouse();
|
|
}
|
|
|
|
m_button_id = button_ids::id_pad_begin;
|
|
UpdateLabels();
|
|
SwitchButtons(true);
|
|
|
|
for (auto but : m_pad_buttons->buttons())
|
|
{
|
|
but->setFocusPolicy(Qt::StrongFocus);
|
|
}
|
|
|
|
for (auto but : ui->buttonBox->buttons())
|
|
{
|
|
but->setFocusPolicy(Qt::StrongFocus);
|
|
}
|
|
|
|
ui->tabWidget->setFocusPolicy(Qt::TabFocus);
|
|
ui->scrollArea->setFocusPolicy(Qt::StrongFocus);
|
|
|
|
ui->chooseProfile->setFocusPolicy(Qt::WheelFocus);
|
|
ui->chooseHandler->setFocusPolicy(Qt::WheelFocus);
|
|
ui->chooseDevice->setFocusPolicy(Qt::WheelFocus);
|
|
ui->chooseClass->setFocusPolicy(Qt::WheelFocus);
|
|
ui->chooseProduct->setFocusPolicy(Qt::WheelFocus);
|
|
}
|
|
|
|
void pad_settings_dialog::RepaintPreviewLabel(QLabel* l, int deadzone, int desired_width, int x, int y, int squircle, double multiplier) const
|
|
{
|
|
desired_width = 100; // Let's keep a fixed size for these labels for now
|
|
const int deadzone_max = m_handler ? m_handler->thumb_max : 255; // 255 used as fallback. The deadzone circle shall be small.
|
|
|
|
constexpr qreal relative_size = 0.9;
|
|
const qreal device_pixel_ratio = devicePixelRatioF();
|
|
const qreal scaled_width = desired_width * device_pixel_ratio;
|
|
const qreal origin = desired_width / 2.0;
|
|
const qreal outer_circle_diameter = relative_size * desired_width;
|
|
const qreal inner_circle_diameter = outer_circle_diameter * deadzone / deadzone_max;
|
|
const qreal outer_circle_radius = outer_circle_diameter / 2.0;
|
|
const qreal inner_circle_radius = inner_circle_diameter / 2.0;
|
|
const qreal stick_x = std::clamp(outer_circle_radius * x * multiplier / deadzone_max, -outer_circle_radius, outer_circle_radius);
|
|
const qreal stick_y = std::clamp(outer_circle_radius * -y * multiplier / deadzone_max, -outer_circle_radius, outer_circle_radius);
|
|
|
|
const bool show_emulated_values = ui->chb_show_emulated_values->isChecked();
|
|
|
|
[[maybe_unused]] qreal ingame_x = 0.0;
|
|
[[maybe_unused]] qreal ingame_y = 0.0;
|
|
|
|
// Set up the canvas for our work of art
|
|
QPixmap pixmap(scaled_width, scaled_width);
|
|
pixmap.setDevicePixelRatio(device_pixel_ratio);
|
|
pixmap.fill(Qt::transparent);
|
|
|
|
// Configure the painter and set its origin
|
|
QPainter painter(&pixmap);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
|
painter.translate(origin, origin);
|
|
painter.setBrush(QBrush(Qt::white));
|
|
|
|
if (show_emulated_values)
|
|
{
|
|
u16 real_x = 0;
|
|
u16 real_y = 0;
|
|
|
|
if (m_handler)
|
|
{
|
|
const int m_in = multiplier * 100.0;
|
|
const u16 normal_x = m_handler->NormalizeStickInput(static_cast<u16>(std::abs(x)), deadzone, m_in, true);
|
|
const u16 normal_y = m_handler->NormalizeStickInput(static_cast<u16>(std::abs(y)), deadzone, m_in, true);
|
|
const s32 x_in = x >= 0 ? normal_x : 0 - normal_x;
|
|
const s32 y_in = y >= 0 ? normal_y : 0 - normal_y;
|
|
m_handler->convert_stick_values(real_x, real_y, x_in, y_in, deadzone, squircle);
|
|
}
|
|
|
|
constexpr qreal real_max = 126;
|
|
ingame_x = std::clamp(outer_circle_radius * (static_cast<qreal>(real_x) - real_max) / real_max, -outer_circle_radius, outer_circle_radius);
|
|
ingame_y = std::clamp(outer_circle_radius * -(static_cast<qreal>(real_y) - real_max) / real_max, -outer_circle_radius, outer_circle_radius);
|
|
|
|
// Draw a black outer squircle that roughly represents the DS3's max values
|
|
QPainterPath path;
|
|
path.addRoundedRect(QRectF(-outer_circle_radius, -outer_circle_radius, outer_circle_diameter, outer_circle_diameter), 5, 5, Qt::SizeMode::RelativeSize);
|
|
painter.setPen(QPen(Qt::black, 1.0));
|
|
painter.drawPath(path);
|
|
}
|
|
|
|
// Draw a black outer circle that represents the maximum for the deadzone
|
|
painter.setPen(QPen(Qt::black, 1.0));
|
|
painter.drawEllipse(QRectF(-outer_circle_radius, -outer_circle_radius, outer_circle_diameter, outer_circle_diameter));
|
|
|
|
// Draw a red inner circle that represents the current deadzone
|
|
painter.setPen(QPen(Qt::red, 1.0));
|
|
painter.drawEllipse(QRectF(-inner_circle_radius, -inner_circle_radius, inner_circle_diameter, inner_circle_diameter));
|
|
|
|
// Draw a blue dot that represents the current stick orientation
|
|
painter.setPen(QPen(Qt::blue, 2.0));
|
|
painter.drawEllipse(QRectF(stick_x - 0.5, stick_y - 0.5, 1.0, 1.0));
|
|
|
|
if (show_emulated_values)
|
|
{
|
|
// Draw a red dot that represents the current ingame stick orientation
|
|
painter.setPen(QPen(Qt::red, 2.0));
|
|
painter.drawEllipse(QRectF(ingame_x - 0.5, ingame_y - 0.5, 1.0, 1.0));
|
|
}
|
|
|
|
l->setPixmap(pixmap);
|
|
}
|
|
|
|
void pad_settings_dialog::keyPressEvent(QKeyEvent *keyEvent)
|
|
{
|
|
if (m_button_id == button_ids::id_pad_begin)
|
|
{
|
|
// We are not remapping a button, so pass the event to the base class.
|
|
QDialog::keyPressEvent(keyEvent);
|
|
return;
|
|
}
|
|
|
|
if (m_handler->m_type != pad_handler::keyboard)
|
|
{
|
|
// Do nothing, we don't want to interfere with the ongoing remapping.
|
|
return;
|
|
}
|
|
|
|
if (keyEvent->isAutoRepeat())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_button_id <= button_ids::id_pad_begin || m_button_id >= button_ids::id_pad_end)
|
|
{
|
|
cfg_log.error("Pad Settings: Handler Type: %d, Unknown button ID: %d", static_cast<int>(m_handler->m_type), m_button_id);
|
|
}
|
|
else
|
|
{
|
|
m_cfg_entries[m_button_id].key = keyboard_pad_handler::GetKeyName(keyEvent);
|
|
m_cfg_entries[m_button_id].text = qstr(m_cfg_entries[m_button_id].key);
|
|
}
|
|
|
|
ReactivateButtons();
|
|
}
|
|
|
|
void pad_settings_dialog::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
if (m_button_id == button_ids::id_pad_begin)
|
|
{
|
|
// We are not remapping a button, so pass the event to the base class.
|
|
QDialog::mouseReleaseEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (m_handler->m_type != pad_handler::keyboard)
|
|
{
|
|
// Do nothing, we don't want to interfere with the ongoing remapping.
|
|
return;
|
|
}
|
|
|
|
if (m_button_id <= button_ids::id_pad_begin || m_button_id >= button_ids::id_pad_end)
|
|
{
|
|
cfg_log.error("Pad Settings: Handler Type: %d, Unknown button ID: %d", static_cast<int>(m_handler->m_type), m_button_id);
|
|
}
|
|
else
|
|
{
|
|
m_cfg_entries[m_button_id].key = (static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(event);
|
|
m_cfg_entries[m_button_id].text = qstr(m_cfg_entries[m_button_id].key);
|
|
}
|
|
|
|
ReactivateButtons();
|
|
}
|
|
|
|
void pad_settings_dialog::wheelEvent(QWheelEvent *event)
|
|
{
|
|
if (m_button_id == button_ids::id_pad_begin)
|
|
{
|
|
// We are not remapping a button, so pass the event to the base class.
|
|
QDialog::wheelEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (m_handler->m_type != pad_handler::keyboard)
|
|
{
|
|
// Do nothing, we don't want to interfere with the ongoing remapping.
|
|
return;
|
|
}
|
|
|
|
if (m_button_id <= button_ids::id_pad_begin || m_button_id >= button_ids::id_pad_end)
|
|
{
|
|
cfg_log.error("Pad Settings: Handler Type: %d, Unknown button ID: %d", static_cast<int>(m_handler->m_type), m_button_id);
|
|
return;
|
|
}
|
|
|
|
const QPoint direction = event->angleDelta();
|
|
|
|
if (direction.isNull())
|
|
{
|
|
// Scrolling started/ended event, no direction given
|
|
return;
|
|
}
|
|
|
|
u32 key;
|
|
|
|
if (const int x = direction.x())
|
|
{
|
|
if (event->inverted() ? x < 0 : x > 0)
|
|
{
|
|
key = mouse::wheel_left;
|
|
}
|
|
else
|
|
{
|
|
key = mouse::wheel_right;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int y = direction.y();
|
|
|
|
if (event->inverted() ? y < 0 : y > 0)
|
|
{
|
|
key = mouse::wheel_up;
|
|
}
|
|
else
|
|
{
|
|
key = mouse::wheel_down;
|
|
}
|
|
}
|
|
m_cfg_entries[m_button_id].key = (static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key);
|
|
m_cfg_entries[m_button_id].text = qstr(m_cfg_entries[m_button_id].key);
|
|
ReactivateButtons();
|
|
}
|
|
|
|
void pad_settings_dialog::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
if (m_button_id == button_ids::id_pad_begin)
|
|
{
|
|
// We are not remapping a button, so pass the event to the base class.
|
|
QDialog::mouseMoveEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (m_handler->m_type != pad_handler::keyboard)
|
|
{
|
|
// Do nothing, we don't want to interfere with the ongoing remapping.
|
|
return;
|
|
}
|
|
|
|
if (m_button_id <= button_ids::id_pad_begin || m_button_id >= button_ids::id_pad_end)
|
|
{
|
|
cfg_log.error("Pad Settings: Handler Type: %d, Unknown button ID: %d", static_cast<int>(m_handler->m_type), m_button_id);
|
|
}
|
|
else
|
|
{
|
|
const QPoint mouse_pos = QCursor::pos();
|
|
const int delta_x = mouse_pos.x() - m_last_pos.x();
|
|
const int delta_y = mouse_pos.y() - m_last_pos.y();
|
|
|
|
u32 key = 0;
|
|
|
|
if (delta_x > 100)
|
|
{
|
|
key = mouse::move_right;
|
|
}
|
|
else if (delta_x < -100)
|
|
{
|
|
key = mouse::move_left;
|
|
}
|
|
else if (delta_y > 100)
|
|
{
|
|
key = mouse::move_down;
|
|
}
|
|
else if (delta_y < -100)
|
|
{
|
|
key = mouse::move_up;
|
|
}
|
|
|
|
if (key != 0)
|
|
{
|
|
m_cfg_entries[m_button_id].key = (static_cast<keyboard_pad_handler*>(m_handler.get()))->GetMouseName(key);
|
|
m_cfg_entries[m_button_id].text = qstr(m_cfg_entries[m_button_id].key);
|
|
ReactivateButtons();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool pad_settings_dialog::eventFilter(QObject* object, QEvent* event)
|
|
{
|
|
switch (event->type())
|
|
{
|
|
case QEvent::MouseButtonRelease:
|
|
{
|
|
// Disabled buttons should not absorb mouseclicks
|
|
event->ignore();
|
|
break;
|
|
}
|
|
case QEvent::MouseMove:
|
|
{
|
|
mouseMoveEvent(static_cast<QMouseEvent*>(event));
|
|
break;
|
|
}
|
|
case QEvent::Enter:
|
|
{
|
|
if (ui->l_description && m_descriptions.contains(object))
|
|
{
|
|
// Check for visibility when entering a widget (needed in case of overlapping widgets in a QStackedWidget for example)
|
|
if (const auto widget = qobject_cast<QWidget*>(object); widget && widget->isVisible())
|
|
{
|
|
ui->l_description->setText(m_descriptions[object]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QEvent::Leave:
|
|
{
|
|
if (ui->l_description && m_descriptions.contains(object))
|
|
{
|
|
ui->l_description->setText(m_description);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return QDialog::eventFilter(object, event);
|
|
}
|
|
|
|
void pad_settings_dialog::UpdateLabels(bool is_reset)
|
|
{
|
|
if (is_reset)
|
|
{
|
|
const cfg_pad& cfg = GetPlayerConfig();
|
|
|
|
// Update device class
|
|
ui->chooseClass->setCurrentIndex(cfg.device_class_type);
|
|
|
|
// Trigger the change manually in case that the class dropdown didn't fire an event
|
|
HandleDeviceClassChange(ui->chooseClass->currentIndex());
|
|
|
|
const auto products = input::get_products_by_class(cfg.device_class_type);
|
|
|
|
for (usz i = 0; i < products.size(); i++)
|
|
{
|
|
if (products[i].vendor_id == cfg.vendor_id && products[i].product_id == cfg.product_id)
|
|
{
|
|
ui->chooseProduct->setCurrentIndex(static_cast<int>(i));
|
|
break;
|
|
}
|
|
}
|
|
|
|
ui->chb_vibration_large->setChecked(static_cast<bool>(cfg.enable_vibration_motor_large));
|
|
ui->chb_vibration_small->setChecked(static_cast<bool>(cfg.enable_vibration_motor_small));
|
|
ui->chb_vibration_switch->setChecked(static_cast<bool>(cfg.switch_vibration_motors));
|
|
|
|
// Update Trigger Thresholds
|
|
ui->preview_trigger_left->setRange(0, m_handler->trigger_max);
|
|
ui->slider_trigger_left->setRange(0, m_handler->trigger_max);
|
|
ui->slider_trigger_left->setValue(cfg.ltriggerthreshold);
|
|
|
|
ui->preview_trigger_right->setRange(0, m_handler->trigger_max);
|
|
ui->slider_trigger_right->setRange(0, m_handler->trigger_max);
|
|
ui->slider_trigger_right->setValue(cfg.rtriggerthreshold);
|
|
|
|
// Update Stick Deadzones
|
|
ui->slider_stick_left->setRange(0, m_handler->thumb_max);
|
|
ui->slider_stick_left->setValue(cfg.lstickdeadzone);
|
|
|
|
ui->slider_stick_right->setRange(0, m_handler->thumb_max);
|
|
ui->slider_stick_right->setValue(cfg.rstickdeadzone);
|
|
|
|
std::vector<std::string> range;
|
|
|
|
// Update Mouse Movement Mode
|
|
const int mouse_movement_index = ui->mouse_movement->findData(static_cast<int>(cfg.mouse_move_mode.get()));
|
|
ensure(mouse_movement_index >= 0);
|
|
ui->mouse_movement->setCurrentIndex(mouse_movement_index);
|
|
|
|
// Update Mouse Deadzones
|
|
range = cfg.mouse_deadzone_x.to_list();
|
|
ui->mouse_dz_x->setRange(std::stoi(range.front()), std::stoi(range.back()));
|
|
ui->mouse_dz_x->setValue(cfg.mouse_deadzone_x);
|
|
|
|
range = cfg.mouse_deadzone_y.to_list();
|
|
ui->mouse_dz_y->setRange(std::stoi(range.front()), std::stoi(range.back()));
|
|
ui->mouse_dz_y->setValue(cfg.mouse_deadzone_y);
|
|
|
|
// Update Mouse Acceleration
|
|
range = cfg.mouse_acceleration_x.to_list();
|
|
ui->mouse_accel_x->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->mouse_accel_x->setValue(cfg.mouse_acceleration_x / 100.0);
|
|
|
|
range = cfg.mouse_acceleration_y.to_list();
|
|
ui->mouse_accel_y->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->mouse_accel_y->setValue(cfg.mouse_acceleration_y / 100.0);
|
|
|
|
// Update Stick Lerp Factors
|
|
range = cfg.l_stick_lerp_factor.to_list();
|
|
ui->left_stick_lerp->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->left_stick_lerp->setValue(cfg.l_stick_lerp_factor / 100.0);
|
|
|
|
range = cfg.r_stick_lerp_factor.to_list();
|
|
ui->right_stick_lerp->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->right_stick_lerp->setValue(cfg.r_stick_lerp_factor / 100.0);
|
|
|
|
// Update Stick Multipliers
|
|
range = cfg.lstickmultiplier.to_list();
|
|
ui->stick_multi_left->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->stick_multi_left->setValue(cfg.lstickmultiplier / 100.0);
|
|
ui->kb_stick_multi_left->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->kb_stick_multi_left->setValue(cfg.lstickmultiplier / 100.0);
|
|
|
|
range = cfg.rstickmultiplier.to_list();
|
|
ui->stick_multi_right->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->stick_multi_right->setValue(cfg.rstickmultiplier / 100.0);
|
|
ui->kb_stick_multi_right->setRange(std::stod(range.front()) / 100.0, std::stod(range.back()) / 100.0);
|
|
ui->kb_stick_multi_right->setValue(cfg.rstickmultiplier / 100.0);
|
|
|
|
// Update Squircle Factors
|
|
range = cfg.lpadsquircling.to_list();
|
|
ui->squircle_left->setRange(std::stoi(range.front()), std::stoi(range.back()));
|
|
ui->squircle_left->setValue(cfg.lpadsquircling);
|
|
|
|
range = cfg.rpadsquircling.to_list();
|
|
ui->squircle_right->setRange(std::stoi(range.front()), std::stoi(range.back()));
|
|
ui->squircle_right->setValue(cfg.rpadsquircling);
|
|
|
|
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, cfg.lpadsquircling, cfg.lstickmultiplier / 100.0);
|
|
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, cfg.rpadsquircling, cfg.rstickmultiplier / 100.0);
|
|
|
|
// Update pressure sensitivity factors
|
|
range = cfg.pressure_intensity.to_list();
|
|
ui->sb_pressure_intensity->setRange(std::stoi(range.front()), std::stoi(range.back()));
|
|
ui->sb_pressure_intensity->setValue(cfg.pressure_intensity);
|
|
|
|
// Apply stored/default LED settings to the device
|
|
m_enable_led = m_handler->has_led();
|
|
SetPadData(0, 0);
|
|
|
|
// Enable battery and LED group box
|
|
m_enable_battery = m_handler->has_battery();
|
|
ui->gb_battery->setVisible(m_enable_battery || m_enable_led);
|
|
}
|
|
|
|
for (auto& entry : m_cfg_entries)
|
|
{
|
|
if (is_reset)
|
|
{
|
|
entry.second.key = *entry.second.cfg_text;
|
|
entry.second.text = qstr(entry.second.key);
|
|
}
|
|
|
|
// The button has to contain at least one character, because it would be square'ish otherwise
|
|
m_pad_buttons->button(entry.first)->setText(entry.second.text.isEmpty() ? QStringLiteral("-") : entry.second.text);
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::SwitchButtons(bool is_enabled)
|
|
{
|
|
m_enable_buttons = is_enabled;
|
|
|
|
ui->chb_show_emulated_values->setEnabled(is_enabled);
|
|
ui->stick_multi_left->setEnabled(is_enabled);
|
|
ui->stick_multi_right->setEnabled(is_enabled);
|
|
ui->kb_stick_multi_left->setEnabled(is_enabled);
|
|
ui->kb_stick_multi_right->setEnabled(is_enabled);
|
|
ui->squircle_left->setEnabled(is_enabled);
|
|
ui->squircle_right->setEnabled(is_enabled);
|
|
ui->gb_pressure_intensity->setEnabled(is_enabled && m_enable_pressure_intensity_button);
|
|
ui->gb_vibration->setEnabled(is_enabled && m_enable_rumble);
|
|
ui->gb_sticks->setEnabled(is_enabled && m_enable_deadzones);
|
|
ui->gb_triggers->setEnabled(is_enabled && m_enable_deadzones);
|
|
ui->gb_battery->setEnabled(is_enabled && (m_enable_battery || m_enable_led));
|
|
ui->pb_battery->setEnabled(is_enabled && m_enable_battery);
|
|
ui->b_led_settings->setEnabled(is_enabled && m_enable_led);
|
|
ui->gb_mouse_movement->setEnabled(is_enabled && m_handler->m_type == pad_handler::keyboard);
|
|
ui->gb_mouse_accel->setEnabled(is_enabled && m_handler->m_type == pad_handler::keyboard);
|
|
ui->gb_mouse_dz->setEnabled(is_enabled && m_handler->m_type == pad_handler::keyboard);
|
|
ui->gb_stick_lerp->setEnabled(is_enabled && m_handler->m_type == pad_handler::keyboard);
|
|
ui->chooseClass->setEnabled(is_enabled && ui->chooseClass->count() > 0);
|
|
ui->chooseProduct->setEnabled(is_enabled && ui->chooseProduct->count() > 0);
|
|
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(is_enabled && m_handler->m_type != pad_handler::keyboard);
|
|
|
|
for (int i = button_ids::id_pad_begin + 1; i < button_ids::id_pad_end; i++)
|
|
{
|
|
m_pad_buttons->button(i)->setEnabled(is_enabled);
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::OnPadButtonClicked(int id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case button_ids::id_led:
|
|
case button_ids::id_pad_begin:
|
|
case button_ids::id_pad_end:
|
|
case button_ids::id_add_profile:
|
|
case button_ids::id_refresh:
|
|
case button_ids::id_ok:
|
|
case button_ids::id_cancel:
|
|
return;
|
|
case button_ids::id_reset_parameters:
|
|
ReactivateButtons();
|
|
GetPlayerConfig().from_default();
|
|
UpdateLabels(true);
|
|
return;
|
|
case button_ids::id_blacklist:
|
|
{
|
|
std::lock_guard lock(m_handler_mutex);
|
|
m_handler->get_next_button_press(m_device_name, nullptr, nullptr, true);
|
|
return;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (auto but : m_pad_buttons->buttons())
|
|
{
|
|
but->setFocusPolicy(Qt::ClickFocus);
|
|
}
|
|
|
|
for (auto but : ui->buttonBox->buttons())
|
|
{
|
|
but->setFocusPolicy(Qt::ClickFocus);
|
|
}
|
|
|
|
ui->tabWidget->setFocusPolicy(Qt::ClickFocus);
|
|
ui->scrollArea->setFocusPolicy(Qt::ClickFocus);
|
|
|
|
ui->chooseProfile->setFocusPolicy(Qt::ClickFocus);
|
|
ui->chooseHandler->setFocusPolicy(Qt::ClickFocus);
|
|
ui->chooseDevice->setFocusPolicy(Qt::ClickFocus);
|
|
ui->chooseClass->setFocusPolicy(Qt::ClickFocus);
|
|
ui->chooseProduct->setFocusPolicy(Qt::ClickFocus);
|
|
|
|
m_last_pos = QCursor::pos();
|
|
|
|
m_button_id = id;
|
|
m_pad_buttons->button(m_button_id)->setText(tr("[ Waiting %1 ]").arg(MAX_SECONDS));
|
|
m_pad_buttons->button(m_button_id)->setPalette(QPalette(Qt::blue));
|
|
m_pad_buttons->button(m_button_id)->grabMouse();
|
|
SwitchButtons(false); // disable all buttons, needed for using Space, Enter and other specific buttons
|
|
m_timer.start(1000);
|
|
}
|
|
|
|
void pad_settings_dialog::OnTabChanged(int index)
|
|
{
|
|
// Apply current config
|
|
ApplyCurrentPlayerConfig(index);
|
|
|
|
// Move layout to the new tab
|
|
ui->tabWidget->widget(index)->setLayout(ui->mainLayout);
|
|
|
|
// Refresh handlers
|
|
RefreshHandlers();
|
|
}
|
|
|
|
void pad_settings_dialog::ChangeHandler()
|
|
{
|
|
// Pause input thread. This means we don't have to lock the handler mutex here.
|
|
pause_input_thread();
|
|
|
|
bool force_enable = false; // enable configs even with disconnected devices
|
|
const u32 player = GetPlayerIndex();
|
|
const bool is_ldd_pad = GetIsLddPad(player);
|
|
|
|
std::string handler;
|
|
std::string device;
|
|
|
|
if (is_ldd_pad)
|
|
{
|
|
handler = fmt::format("%s", pad_handler::null);
|
|
}
|
|
else
|
|
{
|
|
handler = sstr(ui->chooseHandler->currentData().toString());
|
|
device = g_cfg_input.player[player]->device.to_string();
|
|
}
|
|
|
|
auto& cfg = g_cfg_input.player[player]->config;
|
|
|
|
// Change and get this player's current handler.
|
|
if (auto& cfg_handler = g_cfg_input.player[player]->handler; handler != cfg_handler.to_string())
|
|
{
|
|
if (!cfg_handler.from_string(handler))
|
|
{
|
|
cfg_log.error("Failed to convert input string: %s", handler);
|
|
return;
|
|
}
|
|
|
|
// Initialize the new pad config's defaults
|
|
m_handler = pad_thread::GetHandler(g_cfg_input.player[player]->handler);
|
|
pad_thread::InitPadConfig(cfg, cfg_handler, m_handler);
|
|
}
|
|
else
|
|
{
|
|
m_handler = pad_thread::GetHandler(g_cfg_input.player[player]->handler);
|
|
}
|
|
|
|
ensure(m_handler);
|
|
|
|
// Get the handler's currently available devices.
|
|
const auto device_list = m_handler->ListDevices();
|
|
|
|
// Localized tooltips
|
|
const Tooltips tooltips;
|
|
|
|
// Change the description
|
|
switch (m_handler->m_type)
|
|
{
|
|
case pad_handler::null:
|
|
GetPlayerConfig().from_default();
|
|
if (is_ldd_pad)
|
|
m_description = tooltips.gamepad_settings.ldd_pad;
|
|
else
|
|
m_description = tooltips.gamepad_settings.null;
|
|
break;
|
|
case pad_handler::keyboard:
|
|
m_description = tooltips.gamepad_settings.keyboard; break;
|
|
#ifdef _WIN32
|
|
case pad_handler::xinput:
|
|
m_description = tooltips.gamepad_settings.xinput; break;
|
|
case pad_handler::mm:
|
|
m_description = tooltips.gamepad_settings.mmjoy; break;
|
|
case pad_handler::ds3:
|
|
m_description = tooltips.gamepad_settings.ds3_windows; break;
|
|
case pad_handler::ds4:
|
|
m_description = tooltips.gamepad_settings.ds4_windows; break;
|
|
case pad_handler::dualsense:
|
|
m_description = tooltips.gamepad_settings.dualsense_windows; break;
|
|
#elif __linux__
|
|
case pad_handler::ds3:
|
|
m_description = tooltips.gamepad_settings.ds3_linux; break;
|
|
case pad_handler::ds4:
|
|
m_description = tooltips.gamepad_settings.ds4_linux; break;
|
|
case pad_handler::dualsense:
|
|
m_description = tooltips.gamepad_settings.dualsense_linux; break;
|
|
#else
|
|
case pad_handler::ds3:
|
|
m_description = tooltips.gamepad_settings.ds3_other; break;
|
|
case pad_handler::ds4:
|
|
m_description = tooltips.gamepad_settings.ds4_other; break;
|
|
case pad_handler::dualsense:
|
|
m_description = tooltips.gamepad_settings.dualsense_other; break;
|
|
#endif
|
|
#ifdef HAVE_LIBEVDEV
|
|
case pad_handler::evdev:
|
|
m_description = tooltips.gamepad_settings.evdev; break;
|
|
#endif
|
|
}
|
|
ui->l_description->setText(m_description);
|
|
|
|
// Update parameters
|
|
m_min_force = m_handler->vibration_min;
|
|
m_max_force = m_handler->vibration_max;
|
|
|
|
// Reset parameters
|
|
m_lx = 0;
|
|
m_ly = 0;
|
|
m_rx = 0;
|
|
m_ry = 0;
|
|
|
|
// Enable Vibration Checkboxes
|
|
m_enable_rumble = m_handler->has_rumble();
|
|
|
|
// Enable Deadzone Settings
|
|
m_enable_deadzones = m_handler->has_deadzones();
|
|
|
|
// Enable Pressure Sensitivity Settings
|
|
m_enable_pressure_intensity_button = m_handler->has_pressure_intensity_button();
|
|
|
|
// Change our contextual widgets
|
|
ui->left_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0);
|
|
ui->right_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0);
|
|
ui->gb_pressure_intensity->setVisible(m_handler->has_pressure_intensity_button());
|
|
|
|
// Update device dropdown and block signals while doing so
|
|
ui->chooseDevice->blockSignals(true);
|
|
ui->chooseDevice->clear();
|
|
|
|
// Refill the device combobox with currently available devices
|
|
switch (m_handler->m_type)
|
|
{
|
|
#ifdef _WIN32
|
|
case pad_handler::xinput:
|
|
#endif
|
|
case pad_handler::ds3:
|
|
case pad_handler::ds4:
|
|
case pad_handler::dualsense:
|
|
{
|
|
const QString name_string = qstr(m_handler->name_string());
|
|
for (usz i = 1; i <= m_handler->max_devices(); i++) // Controllers 1-n in GUI
|
|
{
|
|
const QString device_name = name_string + QString::number(i);
|
|
ui->chooseDevice->addItem(device_name, QVariant::fromValue(pad_device_info{ sstr(device_name), true }));
|
|
}
|
|
force_enable = true;
|
|
break;
|
|
}
|
|
case pad_handler::null:
|
|
{
|
|
if (is_ldd_pad)
|
|
{
|
|
ui->chooseDevice->setPlaceholderText(tr("Custom Controller"));
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
default:
|
|
{
|
|
for (const auto& device_name : device_list)
|
|
{
|
|
ui->chooseDevice->addItem(qstr(device_name), QVariant::fromValue(pad_device_info{ device_name, true }));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Re-enable signals for device dropdown
|
|
ui->chooseDevice->blockSignals(false);
|
|
|
|
// Handle empty device list
|
|
bool config_enabled = force_enable || (m_handler->m_type != pad_handler::null && ui->chooseDevice->count() > 0);
|
|
|
|
if (config_enabled)
|
|
{
|
|
for (int i = 0; i < ui->chooseDevice->count(); i++)
|
|
{
|
|
if (!ui->chooseDevice->itemData(i).canConvert<pad_device_info>())
|
|
{
|
|
cfg_log.fatal("Cannot convert itemData for index %d and itemText %s", i, sstr(ui->chooseDevice->itemText(i)));
|
|
continue;
|
|
}
|
|
const pad_device_info info = ui->chooseDevice->itemData(i).value<pad_device_info>();
|
|
m_handler->get_next_button_press(info.name,
|
|
[this](u16, std::string, std::string pad_name, u32, pad_preview_values) { SwitchPadInfo(pad_name, true); },
|
|
[this](std::string pad_name) { SwitchPadInfo(pad_name, false); }, false);
|
|
|
|
if (info.name == device)
|
|
{
|
|
ui->chooseDevice->setCurrentIndex(i);
|
|
}
|
|
}
|
|
|
|
// Force Refresh
|
|
ChangeDevice(ui->chooseDevice->currentIndex());
|
|
}
|
|
else
|
|
{
|
|
if (ui->chooseDevice->count() == 0)
|
|
{
|
|
ui->chooseDevice->setPlaceholderText(tr("No Device Detected"));
|
|
}
|
|
}
|
|
|
|
// Handle running timers
|
|
if (m_timer.isActive())
|
|
{
|
|
ReactivateButtons();
|
|
}
|
|
if (m_timer_input.isActive())
|
|
{
|
|
m_timer_input.stop();
|
|
}
|
|
if (m_timer_pad_refresh.isActive())
|
|
{
|
|
m_timer_pad_refresh.stop();
|
|
}
|
|
|
|
// Reload the buttons with the new handler
|
|
ReloadButtons();
|
|
|
|
// Enable configuration if possible
|
|
SwitchButtons(config_enabled && m_handler->m_type == pad_handler::keyboard);
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(!is_ldd_pad);
|
|
ui->chooseDevice->setEnabled(config_enabled && ui->chooseDevice->count() > 0);
|
|
ui->chooseHandler->setEnabled(!is_ldd_pad && ui->chooseHandler->count() > 0);
|
|
|
|
// Re-enable input timer
|
|
if (ui->chooseDevice->isEnabled() && ui->chooseDevice->currentIndex() >= 0)
|
|
{
|
|
start_input_thread();
|
|
m_timer_input.start(1);
|
|
m_timer_pad_refresh.start(1000);
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::ChangeProfile(const QString& profile)
|
|
{
|
|
if (profile.isEmpty())
|
|
return;
|
|
|
|
m_profile = sstr(profile);
|
|
|
|
// Load in order to get the pad handlers
|
|
if (!g_cfg_input.load(m_title_id, m_profile, true))
|
|
{
|
|
cfg_log.notice("Loaded empty pad config");
|
|
}
|
|
|
|
// Adjust to the different pad handlers
|
|
for (usz i = 0; i < g_cfg_input.player.size(); i++)
|
|
{
|
|
std::shared_ptr<PadHandlerBase> handler;
|
|
pad_thread::InitPadConfig(g_cfg_input.player[i]->config, g_cfg_input.player[i]->handler, handler);
|
|
}
|
|
|
|
// Reload with proper defaults
|
|
if (!g_cfg_input.load(m_title_id, m_profile, true))
|
|
{
|
|
cfg_log.notice("Reloaded empty pad config");
|
|
}
|
|
|
|
const u32 player_id = GetPlayerIndex();
|
|
const std::string handler = fmt::format("%s", g_cfg_input.player[player_id]->handler.get());
|
|
|
|
if (const QString q_handler = qstr(handler); ui->chooseHandler->findText(q_handler) >= 0)
|
|
{
|
|
ui->chooseHandler->setCurrentText(q_handler);
|
|
}
|
|
else
|
|
{
|
|
cfg_log.fatal("Handler '%s' not found in handler dropdown.", handler);
|
|
}
|
|
|
|
// Force Refresh
|
|
ChangeHandler();
|
|
}
|
|
|
|
void pad_settings_dialog::ChangeDevice(int index)
|
|
{
|
|
if (index < 0)
|
|
return;
|
|
|
|
const pad_device_info info = ui->chooseDevice->itemData(index).value<pad_device_info>();
|
|
m_device_name = info.name;
|
|
if (!g_cfg_input.player[GetPlayerIndex()]->device.from_string(m_device_name))
|
|
{
|
|
// Something went wrong
|
|
cfg_log.error("Failed to convert device string: %s", m_device_name);
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::HandleDeviceClassChange(int index) const
|
|
{
|
|
if (index < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ui->chooseProduct->clear();
|
|
|
|
for (const auto& product : input::get_products_by_class(index))
|
|
{
|
|
switch (product.type)
|
|
{
|
|
case input::product_type::playstation_3_controller:
|
|
{
|
|
ui->chooseProduct->addItem(tr("PS3 Controller", "PlayStation 3 Controller"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::dance_dance_revolution_mat:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Dance Dance Revolution", "Dance Dance Revolution Mat"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::dj_hero_turntable:
|
|
{
|
|
ui->chooseProduct->addItem(tr("DJ Hero Turntable", "DJ Hero Turntable"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::harmonix_rockband_drum_kit:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Rock Band", "Harmonix Rock Band Drum Kit"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::harmonix_rockband_drum_kit_2:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Rock Band Pro", "Harmonix Rock Band Pro-Drum Kit"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::harmonix_rockband_guitar:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Rock Band", "Harmonix Rock Band Guitar"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::red_octane_gh_drum_kit:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Guitar Hero", "RedOctane Guitar Hero Drum Kit"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::red_octane_gh_guitar:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Guitar Hero", "RedOctane Guitar Hero Guitar"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
case input::product_type::rock_revolution_drum_kit:
|
|
{
|
|
ui->chooseProduct->addItem(tr("Rock Revolution", "Rock Revolution Drum Controller"), static_cast<int>(product.type));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::AddProfile()
|
|
{
|
|
QInputDialog* dialog = new QInputDialog(this);
|
|
dialog->setWindowTitle(tr("Choose a unique name"));
|
|
dialog->setLabelText(tr("Profile Name: "));
|
|
dialog->setFixedSize(500, 100);
|
|
|
|
while (dialog->exec() != QDialog::Rejected)
|
|
{
|
|
const QString profile_name = dialog->textValue();
|
|
|
|
if (profile_name.isEmpty())
|
|
{
|
|
QMessageBox::warning(this, tr("Error"), tr("Name cannot be empty"));
|
|
continue;
|
|
}
|
|
if (profile_name.contains("."))
|
|
{
|
|
QMessageBox::warning(this, tr("Error"), tr("Must choose a name without '.'"));
|
|
continue;
|
|
}
|
|
if (ui->chooseProfile->findText(profile_name) != -1)
|
|
{
|
|
QMessageBox::warning(this, tr("Error"), tr("Please choose a non-existing name"));
|
|
continue;
|
|
}
|
|
if (CreateConfigFile(qstr(rpcs3::utils::get_input_config_dir(m_title_id)), profile_name))
|
|
{
|
|
ui->chooseProfile->addItem(profile_name);
|
|
ui->chooseProfile->setCurrentText(profile_name);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void pad_settings_dialog::RefreshHandlers()
|
|
{
|
|
const u32 player_id = GetPlayerIndex();
|
|
|
|
// Set the current input type from config. Disable signal to have ChangeHandler always executed exactly once
|
|
ui->chooseHandler->blockSignals(true);
|
|
ui->chooseHandler->clear();
|
|
|
|
if (GetIsLddPad(player_id))
|
|
{
|
|
ui->chooseHandler->addItem(tr("Reserved"));
|
|
}
|
|
else
|
|
{
|
|
const std::vector<std::string> str_inputs = g_cfg_input.player[0]->handler.to_list();
|
|
for (usz i = 0; i < str_inputs.size(); i++)
|
|
{
|
|
const QString item_data = qstr(str_inputs[i]);
|
|
ui->chooseHandler->addItem(GetLocalizedPadHandler(item_data, static_cast<pad_handler>(i)), QVariant(item_data));
|
|
}
|
|
|
|
const auto& handler = g_cfg_input.player[player_id]->handler;
|
|
ui->chooseHandler->setCurrentText(GetLocalizedPadHandler(qstr(handler.to_string()), handler));
|
|
}
|
|
|
|
ui->chooseHandler->blockSignals(false);
|
|
|
|
// Force Change
|
|
ChangeHandler();
|
|
}
|
|
|
|
void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
|
|
{
|
|
if (!m_handler || new_player_id < 0 || static_cast<u32>(new_player_id) >= g_cfg_input.player.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_duplicate_buttons[m_last_player_id] = "";
|
|
|
|
auto& player = g_cfg_input.player[m_last_player_id];
|
|
m_last_player_id = new_player_id;
|
|
|
|
// Check for duplicate button choices
|
|
if (m_handler->m_type != pad_handler::null)
|
|
{
|
|
std::set<std::string> unique_keys;
|
|
for (const auto& entry : m_cfg_entries)
|
|
{
|
|
// Let's ignore special keys, unless we're using a keyboard
|
|
if (entry.first == button_ids::id_pressure_intensity && m_handler->m_type != pad_handler::keyboard)
|
|
continue;
|
|
|
|
if (const auto& [it, ok] = unique_keys.insert(entry.second.key); !ok)
|
|
{
|
|
m_duplicate_buttons[m_last_player_id] = entry.second.key;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply buttons
|
|
for (const auto& entry : m_cfg_entries)
|
|
{
|
|
entry.second.cfg_text->from_string(entry.second.key);
|
|
}
|
|
|
|
// Apply rest of config
|
|
auto& cfg = player->config;
|
|
|
|
if (m_handler->m_type == pad_handler::keyboard)
|
|
{
|
|
cfg.lstickmultiplier.set(ui->kb_stick_multi_left->value() * 100);
|
|
cfg.rstickmultiplier.set(ui->kb_stick_multi_right->value() * 100);
|
|
}
|
|
else
|
|
{
|
|
cfg.lstickmultiplier.set(ui->stick_multi_left->value() * 100);
|
|
cfg.rstickmultiplier.set(ui->stick_multi_right->value() * 100);
|
|
}
|
|
|
|
cfg.lpadsquircling.set(ui->squircle_left->value());
|
|
cfg.rpadsquircling.set(ui->squircle_right->value());
|
|
|
|
if (m_handler->has_rumble())
|
|
{
|
|
cfg.enable_vibration_motor_large.set(ui->chb_vibration_large->isChecked());
|
|
cfg.enable_vibration_motor_small.set(ui->chb_vibration_small->isChecked());
|
|
cfg.switch_vibration_motors.set(ui->chb_vibration_switch->isChecked());
|
|
}
|
|
|
|
if (m_handler->has_deadzones())
|
|
{
|
|
cfg.ltriggerthreshold.set(ui->slider_trigger_left->value());
|
|
cfg.rtriggerthreshold.set(ui->slider_trigger_right->value());
|
|
cfg.lstickdeadzone.set(ui->slider_stick_left->value());
|
|
cfg.rstickdeadzone.set(ui->slider_stick_right->value());
|
|
}
|
|
|
|
if (m_handler->has_pressure_intensity_button())
|
|
{
|
|
cfg.pressure_intensity.set(ui->sb_pressure_intensity->value());
|
|
}
|
|
|
|
if (m_handler->m_type == pad_handler::keyboard)
|
|
{
|
|
const int mouse_move_mode = ui->mouse_movement->currentData().toInt();
|
|
ensure(mouse_move_mode >= 0 && mouse_move_mode <= 1);
|
|
cfg.mouse_move_mode.set(static_cast<mouse_movement_mode>(mouse_move_mode));
|
|
cfg.mouse_acceleration_x.set(ui->mouse_accel_x->value() * 100);
|
|
cfg.mouse_acceleration_y.set(ui->mouse_accel_y->value() * 100);
|
|
cfg.mouse_deadzone_x.set(ui->mouse_dz_x->value());
|
|
cfg.mouse_deadzone_y.set(ui->mouse_dz_y->value());
|
|
cfg.l_stick_lerp_factor.set(ui->left_stick_lerp->value() * 100);
|
|
cfg.r_stick_lerp_factor.set(ui->right_stick_lerp->value() * 100);
|
|
}
|
|
|
|
cfg.device_class_type.set(ui->chooseClass->currentIndex());
|
|
|
|
const auto info = input::get_product_info(static_cast<input::product_type>(ui->chooseProduct->currentData().toInt()));
|
|
|
|
cfg.vendor_id.set(info.vendor_id);
|
|
cfg.product_id.set(info.product_id);
|
|
}
|
|
|
|
void pad_settings_dialog::SaveExit()
|
|
{
|
|
ApplyCurrentPlayerConfig(m_last_player_id);
|
|
|
|
for (const auto& [player_id, key] : m_duplicate_buttons)
|
|
{
|
|
if (!key.empty())
|
|
{
|
|
int result = QMessageBox::Yes;
|
|
m_gui_settings->ShowConfirmationBox(
|
|
tr("Warning!"),
|
|
tr("The %0 button <b>%1</b> of <b>Player %2</b> was assigned at least twice.<br>Please consider adjusting the configuration.<br><br>Continue anyway?<br>")
|
|
.arg(qstr(g_cfg_input.player[player_id]->handler.to_string())).arg(qstr(key)).arg(player_id + 1),
|
|
gui::ib_same_buttons, &result, this);
|
|
|
|
if (result == QMessageBox::No)
|
|
return;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
const std::string profile_key = m_title_id.empty() ? g_cfg_profile.global_key : m_title_id;
|
|
|
|
g_cfg_profile.active_profiles.set_value(profile_key, m_profile);
|
|
g_cfg_profile.save();
|
|
|
|
g_cfg_input.save(m_title_id, m_profile);
|
|
|
|
QDialog::accept();
|
|
}
|
|
|
|
void pad_settings_dialog::CancelExit()
|
|
{
|
|
// Reloads configs from file or defaults
|
|
g_cfg_profile.load();
|
|
g_cfg_input.from_default();
|
|
|
|
QDialog::reject();
|
|
}
|
|
|
|
QString pad_settings_dialog::GetLocalizedPadHandler(const QString& original, pad_handler handler)
|
|
{
|
|
switch (handler)
|
|
{
|
|
case pad_handler::null: return tr("Null");
|
|
case pad_handler::keyboard: return tr("Keyboard");
|
|
case pad_handler::ds3: return tr("DualShock 3");
|
|
case pad_handler::ds4: return tr("DualShock 4");
|
|
case pad_handler::dualsense: return tr("DualSense");
|
|
#ifdef _WIN32
|
|
case pad_handler::xinput: return tr("XInput");
|
|
case pad_handler::mm: return tr("MMJoystick");
|
|
#endif
|
|
#ifdef HAVE_LIBEVDEV
|
|
case pad_handler::evdev: return tr("Evdev");
|
|
#endif
|
|
}
|
|
return original;
|
|
}
|
|
|
|
bool pad_settings_dialog::GetIsLddPad(u32 index) const
|
|
{
|
|
// We only check for ldd pads if the current dialog may affect the running application.
|
|
// To simplify this we include the global pad config indiscriminately as well as the relevant custom pad config.
|
|
if (!Emu.IsStopped() && (m_title_id.empty() || m_title_id == Emu.GetTitleID()))
|
|
{
|
|
std::lock_guard lock(pad::g_pad_mutex);
|
|
if (const auto handler = pad::get_current_handler(true))
|
|
{
|
|
ensure(index < handler->GetPads().size());
|
|
|
|
if (const std::shared_ptr<Pad> pad = handler->GetPads().at(index))
|
|
{
|
|
return pad->ldd;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
u32 pad_settings_dialog::GetPlayerIndex() const
|
|
{
|
|
const int player_id = ui->tabWidget->currentIndex();
|
|
ensure(player_id >= 0 && static_cast<u32>(player_id) < g_cfg_input.player.size());
|
|
return static_cast<u32>(player_id);
|
|
}
|
|
|
|
cfg_pad& pad_settings_dialog::GetPlayerConfig() const
|
|
{
|
|
return g_cfg_input.player[GetPlayerIndex()]->config;
|
|
}
|
|
|
|
void pad_settings_dialog::ResizeDialog()
|
|
{
|
|
// Widgets
|
|
const QSize buttons_size(0, ui->buttonBox->sizeHint().height());
|
|
const QSize tabwidget_size = ui->tabWidget->sizeHint();
|
|
|
|
// Spacing
|
|
const int nr_of_spacings = 1; // Number of widgets - 1
|
|
const QSize spacing_size(0, layout()->spacing() * nr_of_spacings);
|
|
|
|
// Margins
|
|
const auto margins = layout()->contentsMargins();
|
|
const QSize margin_size(margins.left() + margins.right(), margins.top() + margins.bottom());
|
|
|
|
resize(tabwidget_size + buttons_size + margin_size + spacing_size);
|
|
}
|
|
|
|
void pad_settings_dialog::SubscribeTooltip(QObject* object, const QString& tooltip)
|
|
{
|
|
m_descriptions[object] = tooltip;
|
|
object->installEventFilter(this);
|
|
}
|
|
|
|
void pad_settings_dialog::SubscribeTooltips()
|
|
{
|
|
// Localized tooltips
|
|
const Tooltips tooltips;
|
|
|
|
SubscribeTooltip(ui->gb_pressure_intensity, tooltips.gamepad_settings.pressure_intensity);
|
|
SubscribeTooltip(ui->gb_squircle, tooltips.gamepad_settings.squircle_factor);
|
|
SubscribeTooltip(ui->gb_stick_multi, tooltips.gamepad_settings.stick_multiplier);
|
|
SubscribeTooltip(ui->gb_kb_stick_multi, tooltips.gamepad_settings.stick_multiplier);
|
|
SubscribeTooltip(ui->gb_vibration, tooltips.gamepad_settings.vibration);
|
|
SubscribeTooltip(ui->gb_sticks, tooltips.gamepad_settings.stick_deadzones);
|
|
SubscribeTooltip(ui->gb_stick_preview, tooltips.gamepad_settings.emulated_preview);
|
|
SubscribeTooltip(ui->gb_triggers, tooltips.gamepad_settings.trigger_deadzones);
|
|
SubscribeTooltip(ui->gb_stick_lerp, tooltips.gamepad_settings.stick_lerp);
|
|
SubscribeTooltip(ui->gb_mouse_accel, tooltips.gamepad_settings.mouse_acceleration);
|
|
SubscribeTooltip(ui->gb_mouse_dz, tooltips.gamepad_settings.mouse_deadzones);
|
|
SubscribeTooltip(ui->gb_mouse_movement, tooltips.gamepad_settings.mouse_movement);
|
|
}
|
|
|
|
void pad_settings_dialog::start_input_thread()
|
|
{
|
|
m_input_thread_state = input_thread_state::active;
|
|
}
|
|
|
|
void pad_settings_dialog::pause_input_thread()
|
|
{
|
|
if (m_input_thread)
|
|
{
|
|
m_input_thread_state = input_thread_state::pausing;
|
|
|
|
while (m_input_thread_state != input_thread_state::paused)
|
|
{
|
|
std::this_thread::sleep_for(1ms);
|
|
}
|
|
}
|
|
}
|