Qt: Implement input profiles

This commit is contained in:
Connor McLaughlin 2022-06-08 22:15:10 +10:00 committed by refractionpcsx2
parent 59412b1673
commit 48d2cb4975
34 changed files with 1134 additions and 331 deletions

View File

@ -60,6 +60,7 @@ target_sources(pcsx2-qt PRIVATE
Settings/ControllerSettingsDialog.cpp
Settings/ControllerSettingsDialog.h
Settings/ControllerSettingsDialog.ui
Settings/ControllerSettingWidgetBinder.h
Settings/CreateMemoryCardDialog.cpp
Settings/CreateMemoryCardDialog.h
Settings/CreateMemoryCardDialog.ui

View File

@ -504,11 +504,12 @@ void EmuThread::reloadInputSources()
std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
SettingsInterface* si = Host::GetSettingsInterface();
SettingsInterface* bindings_si = Host::GetSettingsInterfaceForBindings();
InputManager::ReloadSources(*si, lock);
// skip loading bindings if we're not running, since it'll get done on startup anyway
if (VMManager::HasValidVM())
InputManager::ReloadBindings(*si);
InputManager::ReloadBindings(*si, *bindings_si);
}
void EmuThread::reloadInputBindings()
@ -525,7 +526,8 @@ void EmuThread::reloadInputBindings()
auto lock = Host::GetSettingsLock();
SettingsInterface* si = Host::GetSettingsInterface();
InputManager::ReloadBindings(*si);
SettingsInterface* bindings_si = Host::GetSettingsInterfaceForBindings();
InputManager::ReloadBindings(*si, *bindings_si);
}
void EmuThread::requestDisplaySize(float scale)

View File

@ -690,4 +690,15 @@ namespace QtUtils
{
return str.empty() ? QString() : QString::fromUtf8(str.data(), str.size());
}
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited)
{
if (widget->font().italic() != inherited)
{
QFont new_font(widget->font());
new_font.setItalic(inherited);
widget->setFont(new_font);
}
}
} // namespace QtUtils

View File

@ -76,4 +76,7 @@ namespace QtUtils
/// Converts a std::string_view to a QString safely.
QString StringViewToQString(const std::string_view& str);
/// Sets a widget to italics if the setting value is inherited.
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
} // namespace QtUtils

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>834</width>
<width>833</width>
<height>560</height>
</rect>
</property>
@ -61,35 +61,14 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loadProfile">
<property name="text">
<string>Load Profile</string>
</property>
<property name="icon">
<iconset theme="folder-open-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveProfile">
<property name="text">
<string>Save Profile</string>
</property>
<property name="icon">
<iconset theme="save-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearBindings">
<property name="text">
<string>Clear Bindings</string>
</property>
<property name="icon">
<iconset theme="file-reduce-line"/>
<iconset theme="file-reduce-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>

View File

@ -19,19 +19,18 @@
#include <QtWidgets/QMessageBox>
#include <algorithm>
#include "ControllerBindingWidgets.h"
#include "ControllerSettingsDialog.h"
#include "Settings/ControllerBindingWidgets.h"
#include "Settings/ControllerSettingsDialog.h"
#include "Settings/ControllerSettingWidgetBinder.h"
#include "Settings/SettingsDialog.h"
#include "EmuThread.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
#include "common/StringUtil.h"
#include "pcsx2/HostSettings.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "SettingWidgetBinder.h"
ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port)
: QWidget(parent)
, m_dialog(dialog)
@ -42,7 +41,9 @@ ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSett
populateControllerTypes();
onTypeChanged();
SettingWidgetBinder::BindWidgetToStringSetting(nullptr, m_ui.controllerType, m_config_section, "Type", "None");
ControllerSettingWidgetBinder::BindWidgetToInputProfileString(m_dialog->getProfileSettingsInterface(),
m_ui.controllerType, m_config_section, "Type", PAD::GetDefaultPadType(port));
connect(m_ui.controllerType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ControllerBindingWidget::onTypeChanged);
connect(m_ui.automaticBinding, &QPushButton::clicked, this, &ControllerBindingWidget::doAutomaticBinding);
connect(m_ui.clearBindings, &QPushButton::clicked, this, &ControllerBindingWidget::doClearBindings);
@ -50,6 +51,11 @@ ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSett
ControllerBindingWidget::~ControllerBindingWidget() = default;
QIcon ControllerBindingWidget::getIcon() const
{
return m_current_widget->getIcon();
}
void ControllerBindingWidget::populateControllerTypes()
{
for (const auto& [name, display_name] : PAD::GetControllerTypeNames())
@ -58,15 +64,16 @@ void ControllerBindingWidget::populateControllerTypes()
void ControllerBindingWidget::onTypeChanged()
{
if (m_current_widget)
const bool is_initializing = (m_current_widget == nullptr);
m_controller_type = m_dialog->getStringValue(m_config_section.c_str(), "Type", PAD::GetDefaultPadType(m_port_number));
if (!is_initializing)
{
m_ui.verticalLayout->removeWidget(m_current_widget);
delete m_current_widget;
m_current_widget = nullptr;
}
m_controller_type = Host::GetBaseStringSettingValue(m_config_section.c_str(), "Type");
const int index = m_ui.controllerType->findData(QString::fromStdString(m_controller_type));
if (index >= 0 && index != m_ui.controllerType->currentIndex())
{
@ -80,6 +87,10 @@ void ControllerBindingWidget::onTypeChanged()
m_current_widget = new ControllerBindingWidget_Base(this);
m_ui.verticalLayout->addWidget(m_current_widget, 1);
// no need to do this on first init, only changes
if (!is_initializing)
m_dialog->updateListDescription(m_port_number, this);
}
void ControllerBindingWidget::doAutomaticBinding()
@ -110,15 +121,20 @@ void ControllerBindingWidget::doAutomaticBinding()
void ControllerBindingWidget::doClearBindings()
{
if (QMessageBox::question(QtUtils::GetRootWidget(this), tr("Clear Bindings"),
tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) != QMessageBox::Yes)
tr("Are you sure you want to clear all bindings for this controller? This action cannot be undone.")) != QMessageBox::Yes)
{
return;
}
if (m_dialog->isEditingGlobalSettings())
{
auto lock = Host::GetSettingsLock();
PAD::ClearPortBindings(*Host::Internal::GetBaseSettingsLayer(), m_port_number);
}
else
{
PAD::ClearPortBindings(*m_dialog->getProfileSettingsInterface(), m_port_number);
}
saveAndRefresh();
}
@ -134,10 +150,17 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
}
bool result;
if (m_dialog->isEditingGlobalSettings())
{
auto lock = Host::GetSettingsLock();
result = PAD::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
}
else
{
result = PAD::MapController(*m_dialog->getProfileSettingsInterface(), m_port_number, mapping);
m_dialog->getProfileSettingsInterface()->Save();
g_emu_thread->reloadInputBindings();
}
// force a refresh after mapping
if (result)
@ -162,11 +185,17 @@ ControllerBindingWidget_Base::~ControllerBindingWidget_Base()
{
}
QIcon ControllerBindingWidget_Base::getIcon() const
{
return QIcon::fromTheme("artboard-2-line");
}
void ControllerBindingWidget_Base::initBindingWidgets()
{
const std::string& type = getControllerType();
const std::string& config_section = getConfigSection();
std::vector<std::string> bindings(PAD::GetControllerBinds(type));
SettingsInterface* sif = getDialog()->getProfileSettingsInterface();
for (std::string& binding : bindings)
{
@ -178,7 +207,7 @@ void ControllerBindingWidget_Base::initBindingWidgets()
continue;
}
widget->setKey(config_section, std::move(binding));
widget->initialize(sif, config_section, std::move(binding));
}
const PAD::VibrationCapabilities vibe_caps = PAD::GetControllerVibrationCapabilities(type);
@ -221,13 +250,13 @@ void ControllerBindingWidget_Base::initBindingWidgets()
});
}
SettingWidgetBinder::BindWidgetToNormalizedSetting(nullptr, widget, config_section, "AxisScale", range, 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileNormalized(sif, widget, config_section, "AxisScale", range, 1.0f);
}
if (QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QStringLiteral("SmallMotorScale")); widget)
SettingWidgetBinder::BindWidgetToFloatSetting(nullptr, widget, config_section, "SmallMotorScale", 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, widget, config_section, "SmallMotorScale", 1.0f);
if (QDoubleSpinBox* widget = findChild<QDoubleSpinBox*>(QStringLiteral("LargeMotorScale")); widget)
SettingWidgetBinder::BindWidgetToFloatSetting(nullptr, widget, config_section, "LargeMotorScale", 1.0f);
ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, widget, config_section, "LargeMotorScale", 1.0f);
}
ControllerBindingWidget_DualShock2::ControllerBindingWidget_DualShock2(ControllerBindingWidget* parent)
@ -241,6 +270,11 @@ ControllerBindingWidget_DualShock2::~ControllerBindingWidget_DualShock2()
{
}
QIcon ControllerBindingWidget_DualShock2::getIcon() const
{
return QIcon::fromTheme("gamepad-line");
}
ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance(ControllerBindingWidget* parent)
{
return new ControllerBindingWidget_DualShock2(parent);

View File

@ -32,6 +32,8 @@ public:
ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port);
~ControllerBindingWidget();
QIcon getIcon() const;
__fi ControllerSettingsDialog* getDialog() const { return m_dialog; }
__fi const std::string& getConfigSection() const { return m_config_section; }
__fi const std::string& getControllerType() const { return m_controller_type; }
@ -71,6 +73,8 @@ public:
__fi const std::string& getControllerType() const { return static_cast<ControllerBindingWidget*>(parent())->getControllerType(); }
__fi u32 getPortNumber() const { return static_cast<ControllerBindingWidget*>(parent())->getPortNumber(); }
virtual QIcon getIcon() const;
protected:
void initBindingWidgets();
};
@ -83,6 +87,8 @@ public:
ControllerBindingWidget_DualShock2(ControllerBindingWidget* parent);
~ControllerBindingWidget_DualShock2();
QIcon getIcon() const override;
static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent);
private:

View File

@ -18,23 +18,43 @@
#include "Frontend/InputManager.h"
#include "Settings/ControllerGlobalSettingsWidget.h"
#include "Settings/ControllerSettingsDialog.h"
#include "Settings/ControllerSettingWidgetBinder.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog)
: QWidget(parent)
, m_dialog(dialog)
{
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.enableSDLSource, "InputSources", "SDL", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.enableXInputSource, "InputSources", "XInput", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.multitapPort1, "EmuCore", "MultitapPort0_Enabled", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.multitapPort2, "EmuCore", "MultitapPort1_Enabled", false);
SettingsInterface* sif = dialog->getProfileSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false);
ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false);
if (dialog->isEditingProfile())
{
m_ui.useProfileHotkeyBindings->setChecked(m_dialog->getBoolValue("Pad", "UseProfileHotkeyBindings", false));
connect(m_ui.useProfileHotkeyBindings, &QCheckBox::stateChanged, this, [this](int new_state) {
m_dialog->setBoolValue("Pad", "UseProfileHotkeyBindings", (new_state == Qt::Checked));
emit bindingSetupChanged();
});
}
else
{
// remove profile options from the UI.
m_ui.mainLayout->removeWidget(m_ui.profileSettings);
m_ui.profileSettings->deleteLater();
m_ui.profileSettings = nullptr;
}
connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled);
for (QCheckBox* cb : {m_ui.multitapPort1, m_ui.multitapPort2})
connect(cb, &QCheckBox::stateChanged, this, [this]() { emit multitapModeChanged(); });
connect(cb, &QCheckBox::stateChanged, this, [this]() { emit bindingSetupChanged(); });
updateSDLOptionsEnabled();
}

View File

@ -36,10 +36,11 @@ public:
void removeDeviceFromList(const QString& identifier);
Q_SIGNALS:
void multitapModeChanged();
void bindingSetupChanged();
private:
void updateSDLOptionsEnabled();
Ui::ControllerGlobalSettingsWidget m_ui;
ControllerSettingsDialog* m_dialog;
};

View File

@ -13,7 +13,7 @@
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<layout class="QGridLayout" name="mainLayout" columnstretch="1,0">
<property name="leftMargin">
<number>0</number>
</property>
@ -26,116 +26,138 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>SDL Input Source</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>The SDL input source supports most controllers, and provides advanced functionality for DualShock 4 / DualSense pads in Bluetooth mode (Vibration / LED Control).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableSDLSource">
<property name="text">
<string>Enable SDL Input Source</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableSDLEnhancedMode">
<property name="text">
<string>DualShock 4 / DualSense Enhanced Mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>XInput Source</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableXInputSource">
<property name="text">
<string>Enable XInput Input Source</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Controller Multitap</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="multitapPort1">
<property name="text">
<string>Multitap on Console Port 1</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="multitapPort2">
<property name="text">
<string>Multitap on Console Port 2</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>SDL Input Source</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_2">
<property name="text">
<string>The SDL input source supports most controllers, and provides advanced functionality for DualShock 4 / DualSense pads in Bluetooth mode (Vibration / LED Control).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="enableSDLEnhancedMode">
<property name="text">
<string>DualShock 4 / DualSense Enhanced Mode</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="enableSDLSource">
<property name="text">
<string>Enable SDL Input Source</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<item row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>XInput Source</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="enableXInputSource">
<property name="text">
<string>Enable XInput Input Source</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Controller Multitap</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="multitapPort1">
<property name="text">
<string>Multitap on Console Port 1</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="multitapPort2">
<property name="text">
<string>Multitap on Console Port 2</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="profileSettings">
<property name="title">
<string>Profile Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="useProfileHotkeyBindings">
<property name="text">
<string>Use Per-Profile Hotkeys</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>45</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" rowspan="5">
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Detected Devices</string>

View File

@ -0,0 +1,177 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <optional>
#include <type_traits>
#include <QtCore/QtCore>
#include <QtGui/QAction>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QSlider>
#include <QtWidgets/QSpinBox>
#include "pcsx2/HostSettings.h"
#include "EmuThread.h"
#include "QtHost.h"
#include "SettingWidgetBinder.h"
/// This nastyness is required because input profiles aren't overlaid settings like the rest of them, it's
/// input profile *or* global, not both.
namespace ControllerSettingWidgetBinder
{
/// Interface specific method of BindWidgetToBoolSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileBool(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, bool default_value)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const bool value = sif->GetBoolValue(section.c_str(), key.c_str(), default_value);
Accessor::setBoolValue(widget, value);
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
const bool new_value = Accessor::getBoolValue(widget);
sif->SetBoolValue(section.c_str(), key.c_str(), new_value);
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
const bool value = Host::GetBaseBoolSettingValue(section.c_str(), key.c_str(), default_value);
Accessor::setBoolValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const bool new_value = Accessor::getBoolValue(widget);
QtHost::SetBaseBoolSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
});
}
}
/// Interface specific method of BindWidgetToFloatSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileFloat(SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float default_value)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
Accessor::setBoolValue(widget, value);
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key)]() {
const float new_value = Accessor::getFloatValue(widget);
sif->SetFloatValue(section.c_str(), key.c_str(), new_value);
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
Accessor::setBoolValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const float new_value = Accessor::getFloatValue(widget);
QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
});
}
}
/// Interface specific method of BindWidgetToNormalizedSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileNormalized(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, float range, float default_value)
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const float value = sif->GetFloatValue(section.c_str(), key.c_str(), default_value);
Accessor::setIntValue(widget, static_cast<int>(value * range));
Accessor::connectValueChanged(widget, [sif, widget, section = std::move(section), key = std::move(key), range]() {
const int new_value = Accessor::getIntValue(widget);
sif->SetFloatValue(section.c_str(), key.c_str(), static_cast<float>(new_value) / range);
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
const float value = Host::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value);
Accessor::setIntValue(widget, static_cast<int>(value * range));
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key), range]() {
const float new_value = (static_cast<float>(Accessor::getIntValue(widget)) / range);
QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value);
g_emu_thread->applySettings();
});
}
}
/// Interface specific method of BindWidgetToStringSetting().
template <typename WidgetType>
static void BindWidgetToInputProfileString(
SettingsInterface* sif, WidgetType* widget, std::string section, std::string key, std::string default_value = std::string())
{
using Accessor = SettingWidgetBinder::SettingAccessor<WidgetType>;
if (sif)
{
const QString value(QString::fromStdString(sif->GetStringValue(section.c_str(), key.c_str(), default_value.c_str())));
Accessor::setStringValue(widget, value);
Accessor::connectValueChanged(widget, [widget, sif, section = std::move(section), key = std::move(key)]() {
const QString new_value = Accessor::getStringValue(widget);
if (!new_value.isEmpty())
sif->SetStringValue(section.c_str(), key.c_str(), new_value.toUtf8().constData());
else
sif->DeleteValue(section.c_str(), key.c_str());
sif->Save();
g_emu_thread->reloadGameSettings();
});
}
else
{
const QString value(QString::fromStdString(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str())));
Accessor::setStringValue(widget, value);
Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
const QString new_value = Accessor::getStringValue(widget);
if (!new_value.isEmpty())
QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.toUtf8().constData());
else
QtHost::RemoveBaseSettingValue(section.c_str(), key.c_str());
g_emu_thread->applySettings();
});
}
}
} // namespace ControllerSettingWidgetBinder

View File

@ -22,14 +22,21 @@
#include "Settings/ControllerBindingWidgets.h"
#include "Settings/HotkeySettingsWidget.h"
#include "pcsx2/Frontend/INISettingsInterface.h"
#include "pcsx2/PAD/Host/PAD.h"
#include "pcsx2/Sio.h"
#include "pcsx2/VMManager.h"
#include "common/Assertions.h"
#include "common/FileSystem.h"
#include <array>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QTextEdit>
static constexpr const std::array<char, 4> s_mtap_slot_names = {{'A', 'B', 'C', 'D'}};
ControllerSettingsDialog::ControllerSettingsDialog(QWidget* parent /* = nullptr */)
: QDialog(parent)
{
@ -37,26 +44,23 @@ ControllerSettingsDialog::ControllerSettingsDialog(QWidget* parent /* = nullptr
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// These are preset in the ui file.
m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->addWidget(m_global_settings);
m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->addWidget(m_hotkey_settings);
// add remainder of ports
createPortWidgets();
refreshProfileList();
createWidgets();
m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &ControllerSettingsDialog::onCategoryCurrentRowChanged);
connect(m_ui.currentProfile, &QComboBox::currentIndexChanged, this, &ControllerSettingsDialog::onCurrentProfileChanged);
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsDialog::close);
connect(m_ui.newProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onNewProfileClicked);
connect(m_ui.loadProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onLoadProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsDialog::onDeleteProfileClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsDialog::onRestoreDefaultsClicked);
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this, &ControllerSettingsDialog::onInputDevicesEnumerated);
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsDialog::onInputDeviceConnected);
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &ControllerSettingsDialog::onInputDeviceDisconnected);
connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this, &ControllerSettingsDialog::onVibrationMotorsEnumerated);
connect(m_global_settings, &ControllerGlobalSettingsWidget::multitapModeChanged, this, &ControllerSettingsDialog::createPortWidgets);
// trigger a device enumeration to populate the device list
g_emu_thread->enumerateInputDevices();
g_emu_thread->enumerateVibrationMotors();
@ -91,6 +95,123 @@ void ControllerSettingsDialog::onCategoryCurrentRowChanged(int row)
m_ui.settingsContainer->setCurrentIndex(row);
}
void ControllerSettingsDialog::onCurrentProfileChanged(int index)
{
switchProfile((index == 0) ? 0 : m_ui.currentProfile->itemText(index));
}
void ControllerSettingsDialog::onNewProfileClicked()
{
const QString profile_name(QInputDialog::getText(this, tr("Create Input Profile"), tr("Enter the name for the new input profile:")));
if (profile_name.isEmpty())
return;
std::string profile_path(VMManager::GetInputProfilePath(profile_name.toStdString()));
if (FileSystem::FileExists(profile_path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("A profile with the name '%1' already exists.").arg(profile_name));
return;
}
const int res = QMessageBox::question(this, tr("Create Input Profile"),
tr("Do you want to copy all bindings from the currently-selected profile to the new profile? Selecting No will create a completely empty profile."),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (res == QMessageBox::Cancel)
return;
INISettingsInterface temp_si(std::move(profile_path));
if (res == QMessageBox::Yes)
{
// copy from global or the current profile
if (!m_profile_interface)
{
// from global
auto lock = Host::GetSettingsLock();
PAD::CopyConfiguration(&temp_si, *Host::Internal::GetBaseSettingsLayer(), true, true, false);
}
else
{
// from profile
const bool copy_hotkey_bindings = m_profile_interface->GetBoolValue("Pad", "UseProfileHotkeyBindings", false);
temp_si.SetBoolValue("Pad", "UseProfileHotkeyBindings", copy_hotkey_bindings);
PAD::CopyConfiguration(&temp_si, *m_profile_interface, true, true, copy_hotkey_bindings);
}
}
if (!temp_si.Save())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to save the new profile to '%1'.").arg(QString::fromStdString(temp_si.GetFileName())));
return;
}
refreshProfileList();
switchProfile(profile_name);
}
void ControllerSettingsDialog::onLoadProfileClicked()
{
if (QMessageBox::question(this, tr("Load Input Profile"),
tr("Are you sure you want to load the input profile named '%1'?\n\n"
"All current global bindings will be removed, and the profile bindings loaded.\n\n"
"You cannot undo this action.")
.arg(m_profile_name)) != QMessageBox::Yes)
{
return;
}
{
auto lock = Host::GetSettingsLock();
PAD::CopyConfiguration(Host::Internal::GetBaseSettingsLayer(), *m_profile_interface, true, true, false);
QtHost::QueueSettingsSave();
}
// make it visible
switchProfile({});
}
void ControllerSettingsDialog::onDeleteProfileClicked()
{
if (QMessageBox::question(this, tr("Delete Input Profile"),
tr("Are you sure you want to delete the input profile named '%1'?\n\n"
"You cannot undo this action.")
.arg(m_profile_name)) != QMessageBox::Yes)
{
return;
}
std::string profile_path(VMManager::GetInputProfilePath(m_profile_name.toStdString()));
if (!FileSystem::DeleteFilePath(profile_path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to delete '%1'.").arg(QString::fromStdString(profile_path)));
return;
}
// switch back to global
refreshProfileList();
switchProfile({});
}
void ControllerSettingsDialog::onRestoreDefaultsClicked()
{
if (QMessageBox::question(this, tr("Restore Defaults"),
tr("Are you sure you want to restore the default controller configuration?\n\n"
"All shared bindings and configuration will be lost, but your input profiles will remain.\n\n"
"You cannot undo this action.")) != QMessageBox::Yes)
{
return;
}
// actually restore it
{
auto lock = Host::GetSettingsLock();
PAD::SetDefaultConfig(*Host::Internal::GetBaseSettingsLayer());
QtHost::QueueSettingsSave();
}
// reload all settings
switchProfile({});
}
void ControllerSettingsDialog::onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices)
{
m_device_list = devices;
@ -133,38 +254,105 @@ void ControllerSettingsDialog::onVibrationMotorsEnumerated(const QList<InputBind
}
}
void ControllerSettingsDialog::createPortWidgets()
bool ControllerSettingsDialog::getBoolValue(const char* section, const char* key, bool default_value) const
{
// shouldn't mess with it with something else visible
if (m_profile_interface)
return m_profile_interface->GetBoolValue(section, key, default_value);
else
return Host::GetBaseBoolSettingValue(section, key, default_value);
}
std::string ControllerSettingsDialog::getStringValue(const char* section, const char* key, const char* default_value) const
{
std::string value;
if (m_profile_interface)
value = m_profile_interface->GetStringValue(section, key, default_value);
else
value = Host::GetBaseStringSettingValue(section, key, default_value);
return value;
}
void ControllerSettingsDialog::setBoolValue(const char* section, const char* key, bool value)
{
if (m_profile_interface)
{
QSignalBlocker sb(m_ui.settingsContainer);
QSignalBlocker sb2(m_ui.settingsCategory);
m_ui.settingsContainer->setCurrentIndex(0);
m_ui.settingsCategory->setCurrentRow(0);
m_profile_interface->SetBoolValue(section, key, value);
m_profile_interface->Save();
g_emu_thread->reloadGameSettings();
}
// remove all except global and hotkeys (i.e. first and last)
pxAssert(m_ui.settingsCategory->count() == m_ui.settingsContainer->count());
while (m_ui.settingsContainer->count() > 2)
else
{
delete m_ui.settingsCategory->takeItem(1);
QtHost::SetBaseBoolSettingValue(section, key, value);
g_emu_thread->applySettings();
}
}
QWidget* widget = m_ui.settingsContainer->widget(1);
void ControllerSettingsDialog::setStringValue(const char* section, const char* key, const char* value)
{
if (m_profile_interface)
{
m_profile_interface->SetStringValue(section, key, value);
m_profile_interface->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::SetBaseStringSettingValue(key, section, value);
g_emu_thread->applySettings();
}
}
void ControllerSettingsDialog::clearSettingValue(const char* section, const char* key)
{
if (m_profile_interface)
{
m_profile_interface->DeleteValue(section, key);
m_profile_interface->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::RemoveBaseSettingValue(section, key);
g_emu_thread->applySettings();
}
}
void ControllerSettingsDialog::createWidgets()
{
QSignalBlocker sb(m_ui.settingsContainer);
QSignalBlocker sb2(m_ui.settingsCategory);
while (m_ui.settingsContainer->count() > 0)
{
QWidget* widget = m_ui.settingsContainer->widget(m_ui.settingsContainer->count() - 1);
m_ui.settingsContainer->removeWidget(widget);
delete widget;
widget->deleteLater();
}
// because we can't insert and shuffle everything forward, we need to temporarily remove hotkeys
QListWidgetItem* const hotkeys_row = m_ui.settingsCategory->takeItem(1);
QWidget* const hotkeys_widget = m_ui.settingsContainer->widget(1);
m_ui.settingsContainer->removeWidget(hotkeys_widget);
m_ui.settingsCategory->clear();
m_global_settings = nullptr;
m_hotkey_settings = nullptr;
{
// global settings
QListWidgetItem* item = new QListWidgetItem();
item->setText(tr("Global Settings"));
item->setIcon(QIcon::fromTheme("settings-3-line"));
m_ui.settingsCategory->addItem(item);
m_ui.settingsCategory->setCurrentRow(0);
m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->addWidget(m_global_settings);
connect(m_global_settings, &ControllerGlobalSettingsWidget::bindingSetupChanged, this, &ControllerSettingsDialog::createWidgets);
for (const QPair<QString, QString>& dev : m_device_list)
m_global_settings->addDeviceToList(dev.first, dev.second);
}
// load mtap settings
const std::array<bool, 2> mtap_enabled = {{Host::GetBaseBoolSettingValue("EmuCore", "MultitapPort0_Enabled", false),
Host::GetBaseBoolSettingValue("EmuCore", "MultitapPort1_Enabled", false)}};
const std::array<bool, 2> mtap_enabled = {{getBoolValue("Pad", "MultitapPort1", false),
getBoolValue("Pad", "MultitapPort2", false)}};
// we reorder things a little to make it look less silly for mtap
static constexpr const std::array<char, 4> mtap_slot_names = {{'A', 'B', 'C', 'D'}};
static constexpr const std::array<u32, MAX_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
// create the ports
@ -175,18 +363,103 @@ void ControllerSettingsDialog::createPortWidgets()
if (is_mtap_port && !mtap_enabled[port])
continue;
QListWidgetItem* item = new QListWidgetItem();
item->setText(mtap_enabled[port] ?
(tr("Controller Port %1%2").arg(port + 1).arg(mtap_slot_names[slot])) :
tr("Controller Port %1").arg(port + 1));
item->setIcon(QIcon::fromTheme("gamepad-line"));
m_ui.settingsCategory->addItem(item);
m_port_bindings[global_slot] = new ControllerBindingWidget(m_ui.settingsContainer, this, global_slot);
m_ui.settingsContainer->addWidget(m_port_bindings[global_slot]);
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(m_port_bindings[global_slot]->getControllerType());
const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown"));
QListWidgetItem* item = new QListWidgetItem();
item->setText(mtap_enabled[port] ?
(tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
item->setIcon(m_port_bindings[global_slot]->getIcon());
item->setData(Qt::UserRole, QVariant(global_slot));
m_ui.settingsCategory->addItem(item);
}
// and re-insert hotkeys
m_ui.settingsCategory->addItem(hotkeys_row);
m_ui.settingsContainer->addWidget(hotkeys_widget);
}
// only add hotkeys if we're editing global settings
if (!m_profile_interface || m_profile_interface->GetBoolValue("Pad", "UseProfileHotkeyBindings", false))
{
QListWidgetItem* item = new QListWidgetItem();
item->setText(tr("Hotkeys"));
item->setIcon(QIcon::fromTheme("keyboard-line"));
m_ui.settingsCategory->addItem(item);
m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->addWidget(m_hotkey_settings);
}
m_ui.loadProfile->setEnabled(isEditingProfile());
m_ui.deleteProfile->setEnabled(isEditingProfile());
m_ui.restoreDefaults->setEnabled(isEditingGlobalSettings());
}
void ControllerSettingsDialog::updateListDescription(u32 global_slot, ControllerBindingWidget* widget)
{
for (int i = 0; i < m_ui.settingsCategory->count(); i++)
{
QListWidgetItem* item = m_ui.settingsCategory->item(i);
const QVariant data(item->data(Qt::UserRole));
if (data.type() == QVariant::UInt && data.toUInt() == global_slot)
{
const bool is_mtap_port = sioPadIsMultitapSlot(global_slot);
const auto [port, slot] = sioConvertPadToPortAndSlot(global_slot);
const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false);
const PAD::ControllerInfo* ci = PAD::GetControllerInfo(widget->getControllerType());
const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown"));
item->setText(mtap_enabled ?
(tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) :
tr("Controller Port %1\n%2").arg(port + 1).arg(display_name));
item->setIcon(widget->getIcon());
break;
}
}
}
void ControllerSettingsDialog::refreshProfileList()
{
const std::vector<std::string> names(PAD::GetInputProfileNames());
QSignalBlocker sb(m_ui.currentProfile);
m_ui.currentProfile->clear();
m_ui.currentProfile->addItem(tr("Shared"));
if (isEditingGlobalSettings())
m_ui.currentProfile->setCurrentIndex(0);
for (const std::string& name : names)
{
const QString qname(QString::fromStdString(name));
m_ui.currentProfile->addItem(qname);
if (qname == m_profile_name)
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->count() - 1);
}
}
void ControllerSettingsDialog::switchProfile(const QString& name)
{
QSignalBlocker sb(m_ui.currentProfile);
if (!name.isEmpty())
{
std::string path(VMManager::GetInputProfilePath(name.toStdString()));
if (!FileSystem::FileExists(path.c_str()))
{
QMessageBox::critical(this, tr("Error"), tr("The input profile named '%1' cannot be found.").arg(name));
return;
}
std::unique_ptr<INISettingsInterface> sif(std::make_unique<INISettingsInterface>(std::move(path)));
sif->Load();
m_profile_interface = std::move(sif);
m_ui.currentProfile->setCurrentIndex(m_ui.currentProfile->findText(name));
}
else
{
m_profile_interface.reset();
m_ui.currentProfile->setCurrentIndex(0);
}
m_profile_name = name;
createWidgets();
}

View File

@ -22,11 +22,14 @@
#include <QtCore/QStringList>
#include <QtWidgets/QDialog>
#include <array>
#include <string>
class ControllerGlobalSettingsWidget;
class ControllerBindingWidget;
class HotkeySettingsWidget;
class SettingsInterface;
class ControllerSettingsDialog final : public QDialog
{
Q_OBJECT
@ -48,25 +51,51 @@ public:
ControllerSettingsDialog(QWidget* parent = nullptr);
~ControllerSettingsDialog();
HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; }
__fi HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; }
__fi const QList<QPair<QString, QString>>& getDeviceList() const { return m_device_list; }
__fi const QStringList& getVibrationMotors() const { return m_vibration_motors; }
__fi bool isEditingGlobalSettings() const { return m_profile_name.isEmpty(); }
__fi bool isEditingProfile() const { return !m_profile_name.isEmpty(); }
__fi SettingsInterface* getProfileSettingsInterface() { return m_profile_interface.get(); }
void updateListDescription(u32 global_slot, ControllerBindingWidget* widget);
// Helper functions for updating setting values globally or in the profile.
bool getBoolValue(const char* section, const char* key, bool default_value) const;
std::string getStringValue(const char* section, const char* key, const char* default_value) const;
void setBoolValue(const char* section, const char* key, bool value);
void setStringValue(const char* section, const char* key, const char* value);
void clearSettingValue(const char* section, const char* key);
Q_SIGNALS:
void inputProfileSwitched();
public Q_SLOTS:
void setCategory(Category category);
private Q_SLOTS:
void onCategoryCurrentRowChanged(int row);
void onCurrentProfileChanged(int index);
void onNewProfileClicked();
void onLoadProfileClicked();
void onDeleteProfileClicked();
void onRestoreDefaultsClicked();
void onInputDevicesEnumerated(const QList<QPair<QString, QString>>& devices);
void onInputDeviceConnected(const QString& identifier, const QString& device_name);
void onInputDeviceDisconnected(const QString& identifier);
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
void createPortWidgets();
void createWidgets();
private:
static QIcon getIconForType(const std::string& type);
void refreshProfileList();
void switchProfile(const QString& name);
Ui::ControllerSettingsDialog m_ui;
ControllerGlobalSettingsWidget* m_global_settings = nullptr;
@ -75,4 +104,7 @@ private:
QList<QPair<QString, QString>> m_device_list;
QStringList m_vibration_motors;
QString m_profile_name;
std::unique_ptr<SettingsInterface> m_profile_interface;
};

View File

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1274</width>
<height>668</height>
<width>1276</width>
<height>672</height>
</rect>
</property>
<property name="sizePolicy">
@ -22,7 +22,7 @@
<property name="windowTitle">
<string>PCSX2 Controller Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QListWidget" name="settingsCategory">
<property name="sizePolicy">
@ -49,24 +49,6 @@
<height>32</height>
</size>
</property>
<item>
<property name="text">
<string>Global Settings</string>
</property>
<property name="icon">
<iconset theme="settings-3-line">
<normaloff>.</normaloff>.</iconset>
</property>
</item>
<item>
<property name="text">
<string>Hotkeys</string>
</property>
<property name="icon">
<iconset theme="keyboard-line">
<normaloff>.</normaloff>.</iconset>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
@ -80,11 +62,71 @@
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Profile:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="currentProfile"/>
</item>
<item>
<widget class="QPushButton" name="newProfile">
<property name="text">
<string>New Profile</string>
</property>
<property name="icon">
<iconset theme="file-add-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loadProfile">
<property name="text">
<string>Load Profile</string>
</property>
<property name="icon">
<iconset theme="folder-open-line"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteProfile">
<property name="text">
<string>Delete Profile</string>
</property>
<property name="icon">
<iconset theme="file-reduce-line">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreDefaults">
<property name="text">
<string>Restore Defaults</string>
</property>
<property name="icon">
<iconset theme="restart-line"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -18,11 +18,14 @@
#include "common/StringUtil.h"
#include "Frontend/GameList.h"
#include "PAD/Host/PAD.h"
#include "GameSummaryWidget.h"
#include "SettingsDialog.h"
#include "QtHost.h"
GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsDialog* dialog, QWidget* parent)
: m_dialog(dialog)
{
m_ui.setupUi(this);
@ -39,6 +42,8 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsDialo
}
populateUi(entry);
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
}
GameSummaryWidget::~GameSummaryWidget() = default;
@ -52,4 +57,21 @@ void GameSummaryWidget::populateUi(const GameList::Entry* entry)
m_ui.type->setCurrentIndex(static_cast<int>(entry->type));
m_ui.region->setCurrentIndex(static_cast<int>(entry->region));
m_ui.compatibility->setCurrentIndex(static_cast<int>(entry->compatibility_rating));
for (const std::string& name : PAD::GetInputProfileNames())
m_ui.inputProfile->addItem(QString::fromStdString(name));
std::optional<std::string> profile(m_dialog->getStringValue("EmuCore", "InputProfileName", std::nullopt));
if (profile.has_value())
m_ui.inputProfile->setCurrentIndex(m_ui.inputProfile->findText(QString::fromStdString(profile.value())));
else
m_ui.inputProfile->setCurrentIndex(0);
}
void GameSummaryWidget::onInputProfileChanged(int index)
{
if (index == 0)
m_dialog->setStringSettingValue("EmuCore", "InputProfileName", std::nullopt);
else
m_dialog->setStringSettingValue("EmuCore", "InputProfileName", m_ui.inputProfile->itemText(index).toUtf8());
}

View File

@ -37,5 +37,8 @@ public:
private:
void populateUi(const GameList::Entry* entry);
void onInputProfileChanged(int index);
Ui::GameSummaryWidget m_ui;
SettingsDialog* m_dialog;
};

View File

@ -82,6 +82,65 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="type">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>PS2 Disc</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/media-optical-24.png</normaloff>:/icons/media-optical-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>PS1 Disc</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/media-optical-24.png</normaloff>:/icons/media-optical-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>ELF (PS2 Executable)</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/applications-system-24.png</normaloff>:/icons/applications-system-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Playlist</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/address-book-new-22.png</normaloff>:/icons/address-book-new-22.png</iconset>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
@ -94,6 +153,12 @@
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
@ -251,24 +316,17 @@
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
@ -301,62 +359,44 @@
<property name="text">
<string>Perfect</string>
</property>
</item>z
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<item row="8" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="inputProfile">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Shared</string>
</property>
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Type:</string>
<string>Input Profile:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="type">
<property name="enabled">
<bool>false</bool>
</property>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>PS2 Disc</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/media-optical-24.png</normaloff>:/icons/media-optical-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>PS1 Disc</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/media-optical-24.png</normaloff>:/icons/media-optical-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>ELF (PS2 Executable)</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/applications-system-24.png</normaloff>:/icons/applications-system-24.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Playlist</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/address-book-new-22.png</normaloff>:/icons/address-book-new-22.png</iconset>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<resources>

View File

@ -29,6 +29,7 @@
HotkeySettingsWidget::HotkeySettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog)
: QWidget(parent)
, m_dialog(dialog)
{
createUi();
}
@ -79,6 +80,6 @@ void HotkeySettingsWidget::createButtons()
const int target_row = layout->count() / 2;
layout->addWidget(new QLabel(qApp->translate("Hotkeys", hotkey->display_name), container), target_row, 0);
layout->addWidget(new InputBindingWidget(container, "Hotkeys", hotkey->name), target_row, 1);
layout->addWidget(new InputBindingWidget(container, m_dialog->getProfileSettingsInterface(), "Hotkeys", hotkey->name), target_row, 1);
}
}

View File

@ -37,6 +37,7 @@ private:
void createUi();
void createButtons();
ControllerSettingsDialog* m_dialog;
QTabWidget* m_tab_widget;
struct Category

View File

@ -25,9 +25,10 @@
// _BitScanForward()
#include "pcsx2/GS/GSIntrin.h"
InputBindingDialog::InputBindingDialog(std::string section_name, std::string key_name,
InputBindingDialog::InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name,
std::vector<std::string> bindings, QWidget* parent)
: QDialog(parent)
, m_sif(sif)
, m_section_name(std::move(section_name))
, m_key_name(std::move(key_name))
, m_bindings(std::move(bindings))
@ -186,12 +187,23 @@ void InputBindingDialog::updateList()
void InputBindingDialog::saveListToSettings()
{
if (!m_bindings.empty())
QtHost::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
if (m_sif)
{
if (!m_bindings.empty())
m_sif->SetStringList(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
else
m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str());
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
g_emu_thread->reloadInputBindings();
{
if (!m_bindings.empty())
QtHost::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings);
else
QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
g_emu_thread->reloadInputBindings();
}
}
void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value)

View File

@ -21,12 +21,14 @@
#include <string>
#include <vector>
class SettingsInterface;
class InputBindingDialog : public QDialog
{
Q_OBJECT
public:
InputBindingDialog(std::string section_name, std::string key_name, std::vector<std::string> bindings, QWidget* parent);
InputBindingDialog(SettingsInterface* sif, std::string section_name, std::string key_name, std::vector<std::string> bindings, QWidget* parent);
~InputBindingDialog();
protected Q_SLOTS:
@ -58,6 +60,7 @@ protected:
Ui::InputBindingDialog m_ui;
SettingsInterface* m_sif;
std::string m_section_name;
std::string m_key_name;
std::vector<std::string> m_bindings;

View File

@ -39,7 +39,7 @@ InputBindingWidget::InputBindingWidget(QWidget* parent)
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
}
InputBindingWidget::InputBindingWidget(QWidget* parent, std::string section_name, std::string key_name)
InputBindingWidget::InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, std::string key_name)
: QPushButton(parent)
{
setMinimumWidth(225);
@ -47,7 +47,7 @@ InputBindingWidget::InputBindingWidget(QWidget* parent, std::string section_name
connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked);
setKey(std::move(section_name), std::move(key_name));
initialize(sif, std::move(section_name), std::move(key_name));
}
InputBindingWidget::~InputBindingWidget()
@ -55,12 +55,12 @@ InputBindingWidget::~InputBindingWidget()
Q_ASSERT(!isListeningForInput());
}
void InputBindingWidget::setKey(std::string section_name, std::string key_name)
void InputBindingWidget::initialize(SettingsInterface* sif, std::string section_name, std::string key_name)
{
m_sif = sif;
m_section_name = std::move(section_name);
m_key_name = std::move(key_name);
m_bindings = Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str());
updateText();
reloadBinding();
}
void InputBindingWidget::updateText()
@ -168,8 +168,17 @@ void InputBindingWidget::setNewBinding()
InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size()));
if (!new_binding.empty())
{
QtHost::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
g_emu_thread->reloadInputBindings();
if (m_sif)
{
m_sif->SetStringValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str());
g_emu_thread->reloadInputBindings();
}
}
m_bindings.clear();
@ -179,14 +188,25 @@ void InputBindingWidget::setNewBinding()
void InputBindingWidget::clearBinding()
{
m_bindings.clear();
QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
g_emu_thread->reloadInputBindings();
updateText();
if (m_sif)
{
m_sif->DeleteValue(m_section_name.c_str(), m_key_name.c_str());
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str());
g_emu_thread->reloadInputBindings();
}
reloadBinding();
}
void InputBindingWidget::reloadBinding()
{
m_bindings = Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str());
m_bindings = m_sif ?
m_sif->GetStringList(m_section_name.c_str(), m_key_name.c_str()) :
Host::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str());
updateText();
}
@ -236,7 +256,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
void InputBindingWidget::stopListeningForInput()
{
updateText();
reloadBinding();
delete m_input_listen_timer;
m_input_listen_timer = nullptr;
std::vector<InputBindingKey>().swap(m_new_bindings);
@ -293,7 +313,7 @@ void InputBindingWidget::unhookInputManager()
void InputBindingWidget::openDialog()
{
InputBindingDialog binding_dialog(m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this));
InputBindingDialog binding_dialog(m_sif, m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this));
binding_dialog.exec();
reloadBinding();
}

View File

@ -22,6 +22,7 @@
class QTimer;
class ControllerSettingsDialog;
class SettingsInterface;
class InputBindingWidget : public QPushButton
{
@ -29,10 +30,10 @@ class InputBindingWidget : public QPushButton
public:
InputBindingWidget(QWidget* parent);
InputBindingWidget(QWidget* parent, std::string section_name, std::string key_name);
InputBindingWidget(QWidget* parent, SettingsInterface* sif, std::string section_name, std::string key_name);
~InputBindingWidget();
void setKey(std::string section_name, std::string key_name);
void initialize(SettingsInterface* sif, std::string section_name, std::string key_name);
public Q_SLOTS:
void clearBinding();
@ -65,6 +66,7 @@ protected:
void hookInputManager();
void unhookInputManager();
SettingsInterface* m_sif = nullptr;
std::string m_section_name;
std::string m_key_name;
std::vector<std::string> m_bindings;

View File

@ -363,13 +363,13 @@ void SettingsDialog::setBoolSettingValue(const char* section, const char* key, s
{
value.has_value() ? m_sif->SetBoolValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
value.has_value() ? QtHost::SetBaseBoolSettingValue(section, key, value.value()) : QtHost::RemoveBaseSettingValue(section, key);
g_emu_thread->applySettings();
}
g_emu_thread->applySettings();
}
void SettingsDialog::setIntSettingValue(const char* section, const char* key, std::optional<int> value)
@ -378,13 +378,13 @@ void SettingsDialog::setIntSettingValue(const char* section, const char* key, st
{
value.has_value() ? m_sif->SetIntValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
value.has_value() ? QtHost::SetBaseIntSettingValue(section, key, value.value()) : QtHost::RemoveBaseSettingValue(section, key);
g_emu_thread->applySettings();
}
g_emu_thread->applySettings();
}
void SettingsDialog::setFloatSettingValue(const char* section, const char* key, std::optional<float> value)
@ -393,13 +393,13 @@ void SettingsDialog::setFloatSettingValue(const char* section, const char* key,
{
value.has_value() ? m_sif->SetFloatValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
value.has_value() ? QtHost::SetBaseFloatSettingValue(section, key, value.value()) : QtHost::RemoveBaseSettingValue(section, key);
g_emu_thread->applySettings();
}
g_emu_thread->applySettings();
}
void SettingsDialog::setStringSettingValue(const char* section, const char* key, std::optional<const char*> value)
@ -408,13 +408,13 @@ void SettingsDialog::setStringSettingValue(const char* section, const char* key,
{
value.has_value() ? m_sif->SetStringValue(section, key, value.value()) : m_sif->DeleteValue(section, key);
m_sif->Save();
g_emu_thread->reloadGameSettings();
}
else
{
value.has_value() ? QtHost::SetBaseStringSettingValue(section, key, value.value()) : QtHost::RemoveBaseSettingValue(section, key);
g_emu_thread->applySettings();
}
g_emu_thread->applySettings();
}
void SettingsDialog::openGamePropertiesDialog(const GameList::Entry* game, const std::string_view& serial, u32 crc)

View File

@ -194,6 +194,7 @@
<QtMoc Include="Settings\DEV9DnsHostDialog.h" />
<QtMoc Include="Settings\DEV9SettingsWidget.h" />
<QtMoc Include="Settings\DEV9UiCommon.h" />
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h" />
<ClInclude Include="Settings\HddCreateQt.h" />
<QtMoc Include="Settings\GameSummaryWidget.h" />
<QtMoc Include="Settings\CreateMemoryCardDialog.h" />
@ -331,4 +332,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(SolutionDir)common\vsprops\QtCompile.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>
</Project>

View File

@ -231,6 +231,9 @@
<ClInclude Include="Settings\HddCreateQt.h">
<Filter>Settings</Filter>
</ClInclude>
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h">
<Filter>Settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<QtMoc Include="MainWindow.h" />
@ -392,4 +395,4 @@
<Filter>Tools\Input Recording</Filter>
</QtUi>
</ItemGroup>
</Project>
</Project>

View File

@ -203,7 +203,7 @@ void INISettingsInterface::ClearSection(const char* section)
m_ini.SetValue(section, nullptr, nullptr);
}
std::vector<std::string> INISettingsInterface::GetStringList(const char* section, const char* key)
std::vector<std::string> INISettingsInterface::GetStringList(const char* section, const char* key) const
{
std::list<CSimpleIniA::Entry> entries;
if (!m_ini.GetAllValues(section, key, entries))

View File

@ -52,7 +52,7 @@ public:
void DeleteValue(const char* section, const char* key) override;
void ClearSection(const char* section) override;
std::vector<std::string> GetStringList(const char* section, const char* key) override;
std::vector<std::string> GetStringList(const char* section, const char* key) const override;
void SetStringList(const char* section, const char* key, const std::vector<std::string>& items) override;
bool RemoveFromStringList(const char* section, const char* key, const char* item) override;
bool AddToStringList(const char* section, const char* key, const char* item) override;

View File

@ -808,7 +808,7 @@ bool InputManager::DoEventHook(InputBindingKey key, float value)
// Binding Updater
// ------------------------------------------------------------------------
void InputManager::ReloadBindings(SettingsInterface& si)
void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& binding_si)
{
PauseVibration();
@ -817,10 +817,14 @@ void InputManager::ReloadBindings(SettingsInterface& si)
s_binding_map.clear();
s_pad_vibration_array.clear();
AddHotkeyBindings(si);
// Hotkeys use the base configuration, except if the custom hotkeys option is enabled.
const bool use_profile_hotkeys = si.GetBoolValue("Pad", "UseProfileHotkeyBindings", false);
AddHotkeyBindings(use_profile_hotkeys ? binding_si : si);
// If there's an input profile, we load pad bindings from it alone, rather than
// falling back to the base configuration.
for (u32 pad = 0; pad < PAD::NUM_CONTROLLER_PORTS; pad++)
AddPadBindings(si, pad, PAD::GetDefaultPadType(pad));
AddPadBindings(binding_si, pad, PAD::GetDefaultPadType(pad));
}
// ------------------------------------------------------------------------

View File

@ -223,7 +223,7 @@ namespace InputManager
GenericInputBindingMapping GetGenericBindingMapping(const std::string_view& device);
/// Re-parses the config and registers all hotkey and pad bindings.
void ReloadBindings(SettingsInterface& si);
void ReloadBindings(SettingsInterface& si, SettingsInterface& binding_si);
/// Re-parses the sources part of the config and initializes any backends.
void ReloadSources(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock);

View File

@ -15,6 +15,8 @@
#include "PrecompiledHeader.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "common/SettingsInterface.h"
@ -184,6 +186,9 @@ void PAD::LoadConfig(const SettingsInterface& si)
{
PAD::s_macro_buttons = {};
EmuConfig.MultitapPort0_Enabled = si.GetBoolValue("Pad", "MultitapPort1", false);
EmuConfig.MultitapPort1_Enabled = si.GetBoolValue("Pad", "MultitapPort2", false);
// This is where we would load controller types, if onepad supported them.
for (u32 i = 0; i < NUM_CONTROLLER_PORTS; i++)
{
@ -232,6 +237,8 @@ void PAD::SetDefaultConfig(SettingsInterface& si)
si.SetBoolValue("InputSources", "SDL", true);
si.SetBoolValue("InputSources", "SDLControllerEnhancedMode", false);
si.SetBoolValue("InputSources", "XInput", false);
si.SetBoolValue("Pad", "MultitapPort1", false);
si.SetBoolValue("Pad", "MultitapPort2", false);
// PCSX2 Controller Settings - Controller 1 / Controller 2 / ...
// Use the automapper to set this up.
@ -386,6 +393,62 @@ void PAD::ClearPortBindings(SettingsInterface& si, u32 port)
si.DeleteValue(section.c_str(), info->bindings[i].name);
}
void PAD::CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config, bool copy_pad_bindings, bool copy_hotkey_bindings)
{
if (copy_pad_config)
{
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort1");
dest_si->CopyBoolValue(src_si, "Pad", "MultitapPort2");
}
for (u32 port = 0; port < NUM_CONTROLLER_PORTS; port++)
{
const std::string section(fmt::format("Pad{}", port + 1));
const std::string type(src_si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(port)));
if (copy_pad_config)
dest_si->SetStringValue(section.c_str(), "Type", type.c_str());
const ControllerInfo* info = GetControllerInfo(type);
if (!info)
return;
if (copy_pad_bindings)
{
for (u32 i = 0; i < info->num_bindings; i++)
{
const ControllerBindingInfo& bi = info->bindings[i];
dest_si->CopyStringListValue(src_si, section.c_str(), bi.name);
}
for (u32 i = 0; i < NUM_MACRO_BUTTONS_PER_CONTROLLER; i++)
{
dest_si->CopyStringListValue(src_si, section.c_str(), fmt::format("Macro{}", i + 1).c_str());
dest_si->CopyStringValue(src_si, section.c_str(), fmt::format("Macro{}Binds", i + 1).c_str());
dest_si->CopyUIntValue(src_si, section.c_str(), fmt::format("Macro{}Frequency", i + 1).c_str());
}
}
if (copy_pad_config)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "AxisScale");
if (info->vibration_caps != VibrationCapabilities::NoVibration)
{
dest_si->CopyFloatValue(src_si, section.c_str(), "LargeMotorScale");
dest_si->CopyFloatValue(src_si, section.c_str(), "SmallMotorScale");
}
}
}
if (copy_hotkey_bindings)
{
std::vector<const HotkeyInfo*> hotkeys(InputManager::GetHotkeyList());
for (const HotkeyInfo* hki : hotkeys)
dest_si->CopyStringListValue(src_si, "Hotkeys", hki->name);
}
}
PAD::VibrationCapabilities PAD::GetControllerVibrationCapabilities(const std::string_view& type)
{
const ControllerInfo* info = GetControllerInfo(type);
@ -520,6 +583,20 @@ void PAD::SetMacroButtonState(u32 pad, u32 index, bool state)
}
}
std::vector<std::string> PAD::GetInputProfileNames()
{
FileSystem::FindResultsArray results;
FileSystem::FindFiles(EmuFolders::InputProfiles.c_str(), "*.ini",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS,
&results);
std::vector<std::string> ret;
ret.reserve(results.size());
for (FILESYSTEM_FIND_DATA& fd : results)
ret.emplace_back(Path::GetFileTitle(fd.FileName));
return ret;
}
void PAD::ApplyMacroButton(u32 pad, const MacroButton& mb)
{
const float value = mb.toggle_state ? 1.0f : 0.0f;

View File

@ -99,6 +99,10 @@ namespace PAD
/// Clears all bindings for a given port.
void ClearPortBindings(SettingsInterface& si, u32 port);
/// Copies pad configuration from one interface (ini) to another.
void CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_si,
bool copy_pad_config = true, bool copy_pad_bindings = true, bool copy_hotkey_bindings = true);
/// Updates vibration and other internal state. Called at the *end* of a frame.
void Update();
@ -124,4 +128,7 @@ namespace PAD
/// Sets the state of the specified macro button.
void SetMacroButtonState(u32 pad, u32 index, bool state);
/// Returns a list of input profiles available.
std::vector<std::string> GetInputProfileNames();
} // namespace PAD

View File

@ -1067,8 +1067,11 @@ void Pcsx2Config::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBool(SavestateZstdCompression);
SettingsWrapBitBool(McdEnableEjection);
SettingsWrapBitBool(McdFolderAutoManage);
#ifndef PCSX2_CORE
// We put mtap in the Pad section for Qt to make it easier to manually edit input profiles.
SettingsWrapBitBool(MultitapPort0_Enabled);
SettingsWrapBitBool(MultitapPort1_Enabled);
#endif
// Process various sub-components:

View File

@ -300,11 +300,12 @@ void VMManager::LoadSettings()
{
std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
SettingsInterface* si = Host::GetSettingsInterface();
SettingsInterface* binding_si = Host::GetSettingsInterfaceForBindings();
SettingsLoadWrapper slw(*si);
EmuConfig.LoadSave(slw);
PAD::LoadConfig(*si);
PAD::LoadConfig(*binding_si);
InputManager::ReloadSources(*si, lock);
InputManager::ReloadBindings(*si);
InputManager::ReloadBindings(*si, *binding_si);
// Remove any user-specified hacks in the config (we don't want stale/conflicting values when it's globally disabled).
EmuConfig.GS.MaskUserHacks();
@ -425,7 +426,7 @@ bool VMManager::UpdateGameSettingsLayer()
std::string input_profile_name;
if (new_interface)
new_interface->GetStringValue("Pad", "InputProfileName", &input_profile_name);
new_interface->GetStringValue("EmuCore", "InputProfileName", &input_profile_name);
if (!s_game_settings_interface && !new_interface && s_input_profile_name == input_profile_name)
return false;