Merge branch 'PCSX2:master' into master

This commit is contained in:
987123879113 2022-06-12 08:30:46 +09:00 committed by GitHub
commit b65c398351
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1503 additions and 455 deletions

2
.github/labeler.yml vendored
View File

@ -40,8 +40,6 @@
'GameDB':
- '**/GameIndex.*'
'Installer | Package':
- 'nsis/*'
- 'nsis/**/*'
- 'build.sh'
# Tools / Features

View File

@ -378,6 +378,9 @@ SCAJ-10007:
SCAJ-10008:
name: "Taiko no Tatsujin - Atsumare! Matsuri da!! Yondaime"
region: "NTSC-Unk"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SCAJ-10009:
name: "Psikyo Shooting Collection Vol.1 - Strikers 1&2"
region: "NTSC-Unk"
@ -392,6 +395,7 @@ SCAJ-10012:
name: "Taiko Drum Master"
region: "NTSC-Unk"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SCAJ-10013:
name: "Taiko no Tatsujin - Tobikkiri! Anime Special"
@ -400,8 +404,11 @@ SCAJ-10014:
name: "Taiko no Tatsujin Wai Wai Happy Rokudaime"
region: "NTSC-Unk"
SCAJ-10015:
name: "Taiko No Tatsujin Doka! To Omori 7Daime"
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime"
region: "NTSC-Unk"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SCAJ-20001:
name: "Ratchet & Clank"
region: "NTSC-Unk"
@ -3347,7 +3354,7 @@ SCES-52756:
name: "DJ - Decks & FX - Claudio Coccoluto Edition"
region: "PAL-I"
SCES-52758:
name: " Getaway, The - Black Monday"
name: "Getaway, The - Black Monday"
region: "PAL-M6"
gsHWFixes:
textureInsideRT: 1
@ -3392,7 +3399,7 @@ SCES-52930:
region: "PAL-M5"
compat: 2
SCES-52948:
name: " Getaway, The - Black Monday"
name: "Getaway, The - Black Monday"
region: "PAL-M4"
gsHWFixes:
textureInsideRT: 1
@ -8030,6 +8037,9 @@ SCUS-97623:
name: "Secret Agent Clank"
region: "NTSC-U"
compat: 5
SCUS-97625:
name: "NBA 09 - The Inside"
region: "NTSC-U"
SCUS-97626:
name: "SingStar '90s"
region: "NTSC-U"
@ -8081,6 +8091,10 @@ SCUS-97654:
name: "MotorStorm Arctic Edge"
region: "NTSC-U"
compat: 5
SCUS-97657:
name: "MLB 11 - The Show"
region: "NTSC-U"
compat: 5
SCUS-97660:
name: "SingStar Latino"
region: "NTSC-U"
@ -11822,7 +11836,7 @@ SLES-51615:
region: "PAL-M5"
SLES-51616:
name: "Virtua Fighter 4 Evolution"
region: "PAL-UM5"
region: "PAL-M5"
compat: 5
SLES-51617:
name: "Starsky & Hutch"
@ -18283,6 +18297,10 @@ SLES-54478:
SLES-54483:
name: "Fast and the Furious, The"
region: "PAL-E"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes bluriness.
halfPixelOffset: 1 # Fixes bluriness.
SLES-54485:
name: "Cinderella"
region: "PAL-M3"
@ -24401,12 +24419,6 @@ SLPM-62717:
SLPM-62718:
name: "Sega Ages 2500 Series Vol.27 - Panzer Dragoon"
region: "NTSC-J"
patches:
8D984276:
content: |-
// By PSI
comment=Stop screen shaking (game switches to Progressive)
patch=1,EE,20158558,extended,30420000
SLPM-62719:
name: "Nobunaga no Yabou - Tenka Souyo"
region: "NTSC-J"
@ -25261,7 +25273,7 @@ SLPM-65209:
patch=1,IOP,000251A8,word,00000000
patch=1,IOP,000251AC,word,00000000
patch=1,IOP,000251B0,word,00000000
//Execute removed iReferEventFlagStatus
//Execute removed iReferEventFlagStatus
patch=1,IOP,000251B4,word,3C040003 //lui a0,0x0003
patch=1,IOP,000251B8,word,8C84D368 //lw a0,-0x2C98(a0)
patch=1,IOP,000251BC,word,0C00AFD3 //jal pos_0002BF4C iReferEventFlagStatus
@ -32218,6 +32230,9 @@ SLPS-20215:
SLPS-20216:
name: "Air Ranger - Rescue Helicopter"
region: "NTSC-J"
SLPS-20217:
name: "Hokka Hoka Sentou"
region: "NTSC-J"
SLPS-20218:
name: "Ninja Assault"
region: "NTSC-J"
@ -32233,6 +32248,9 @@ SLPS-20219:
author=kr_ps2
patch=1,EE,0011836C,word,00000000
patch=1,EE,00118374,word,00000000
SLPS-20220:
name: "Pachi-Slot Aruze Oukoku 7 (Disc 1) (Ekishou Disc)"
region: "NTSC-J"
SLPS-20221:
name: "Taiko no Tatsujin [with Tatacon Reproduction Controller]"
region: "NTSC-J"
@ -32273,13 +32291,23 @@ SLPS-20250:
SLPS-20251:
name: "Makai Senki Disgaea"
region: "NTSC-J"
SLPS-20255:
name: "Million God"
region: "NTSC-J"
SLPS-20256:
name: "Cool Shot - Yukawa Keiko"
region: "NTSC-J"
compat: 5
SLPS-20258:
name: "Yamasa Digi World 4D"
SLPS-20257:
name: "Yamasa Digi World 4DX"
region: "NTSC-J"
gameFixes:
- SoftwareRendererFMVHack # Fix flickering and bad textures in FMV.
SLPS-20258:
name: "Yamasa Digi World 4"
region: "NTSC-J"
gameFixes:
- SoftwareRendererFMVHack # Fix flickering and bad textures in FMV.
SLPS-20259:
name: "Kotoba no Puzzle - Mojipittan"
region: "NTSC-J"
@ -32535,7 +32563,7 @@ SLPS-20379:
name: "Eikan wa Kimi ni 2004 - Koushien no Kodou [Artdink Best Choice]"
region: "NTSC-J"
SLPS-20380:
name: "Shinkon Gattai Gondannar!!"
name: "Shinkon Gattai Godannar!!"
region: "NTSC-J"
SLPS-20381:
name: "Monkey Turn V"
@ -32547,6 +32575,9 @@ SLPS-20382:
SLPS-20383:
name: "Taiko no Tatsujin - Atsumare! Matsuri da!! Yondaime"
region: "NTSC-J"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLPS-20384:
name: "Hayarigami - Keishichou Kaii Jiken File"
region: "NTSC-J"
@ -32625,15 +32656,17 @@ SLPS-20412:
name: "Hissatsu Pachinko Station v10"
region: "NTSC-J"
SLPS-20413:
name: "Taiko no Tatsujin - Taiko Drum Masters [with Drum Controller]"
name: "Taiko no Tatsujin - Taiko Drum Master [with Tatacon Controller]"
region: "NTSC-J"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLPS-20414:
name: "Taiko no Tatsujin - Taiko Drum Masters"
name: "Taiko no Tatsujin - Taiko Drum Master"
region: "NTSC-J"
compat: 5
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLPS-20416:
name: "Inyou Taisenki - Byakko Enbu [with EyeToy]"
@ -32834,11 +32867,17 @@ SLPS-20484:
name: "Simple 2000 Series Ultimate Vol. 34 - Sakigake!! Otokojuku"
region: "NTSC-J"
SLPS-20485:
name: "Taiko no Tatsujin Doka! [with Tatacon Controller]"
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime [with Tatacon Controller]"
region: "NTSC-J"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLPS-20486:
name: "Taiko no Tatsujin Bang Tap! Toomori 7 Daimei"
name: "Taiko no Tatsujin - Doka! to Oomori Nanadaime"
region: "NTSC-J"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLPS-20487:
name: "Pachi-Slot King! Kagaku Ninja-Tai Gatchaman"
region: "NTSC-J"
@ -39968,8 +40007,8 @@ SLUS-20758:
content: |-
author=kozarovv
// Growlanser Generations (2 and 3), When game fail at sceCdReadClock due to bad sema/thread state, it will just use 0 as timestamp.
// That make issues with next saves, and finally lead to freeze.
// Patch force game to use WaitSema instead of PollSema in problematic place, that force thread rescheduling.
// That make issues with next saves, and finally lead to freeze.
// Patch force game to use WaitSema instead of PollSema in problematic place, that force thread rescheduling.
patch=1,EE,001153DC,word,0C042618
comment=IPU freeze fix
patch=0,EE,00109d04,word,00000000
@ -39983,8 +40022,8 @@ SLUS-20759:
// CRC 4AD529BB, 4CD3663F
author=kozarovv
// Growlanser Generations (2 and 3), When game fail at sceCdReadClock due to bad sema/thread state, it will just use 0 as timestamp.
// That make issues with next saves, and finally lead to freeze.
// Patch force game to use WaitSema instead of PollSema in problematic place, that force thread rescheduling.
// That make issues with next saves, and finally lead to freeze.
// Patch force game to use WaitSema instead of PollSema in problematic place, that force thread rescheduling.
patch=1,EE,00114CBC,word,0C042618
comment=IPU freeze fix
patch=0,EE,00109d04,word,00000000
@ -40152,6 +40191,7 @@ SLUS-20800:
name: "Taiko Drum Master"
region: "NTSC-U"
gsHWFixes:
deinterlace: 4 # Game requires bob bff deinterlacing when auto.
alignSprite: 1 # Fixes vertical lines.
SLUS-20801:
name: "Midway Arcade Treasures"
@ -43407,9 +43447,12 @@ SLUS-21448:
region: "NTSC-U"
compat: 5
SLUS-21449:
name: "Fast and the Furious, The - Tokyo Drift"
name: "Fast and the Furious, The"
region: "NTSC-U"
compat: 5
gsHWFixes:
mergeSprite: 1 # Fixes bluriness.
halfPixelOffset: 1 # Fixes bluriness.
SLUS-21450:
name: "Super PickUps"
region: "NTSC-U"
@ -44424,6 +44467,9 @@ SLUS-21694:
name: "Final Fantasy XI - Wings of the Goddess"
region: "NTSC-U"
compat: 3
SLUS-21696:
name: "Ford Racing - Off Road"
region: "NTSC-U"
SLUS-21697:
name: "Iridium Runners"
region: "NTSC-U"
@ -44464,6 +44510,9 @@ SLUS-21704:
name: "Final Fantasy XI - Vana'diel Collection 2008"
region: "NTSC-U"
compat: 3
SLUS-21705:
name: "Madden NFL 08"
region: "NTSC-U"
SLUS-21706:
name: "Alvin and the Chipmunks"
region: "NTSC-U"
@ -44509,6 +44558,11 @@ SLUS-21712:
name: "History Channel - Battle for the Pacific"
region: "NTSC-U"
compat: 5
SLUS-21713:
name: "Winter Sports 2008 - The Ultimate Challenge"
region: "NTSC-U"
clampModes:
vuClampMode: 3 # Fixes SPS.
SLUS-21714:
name: "Baroque"
region: "NTSC-U"
@ -44810,6 +44864,9 @@ SLUS-21776:
name: "FIFA 2009"
region: "NTSC-U"
compat: 5
SLUS-21777:
name: "NBA Live '09"
region: "NTSC-U"
SLUS-21778:
name: "Dokapon Kingdom"
region: "NTSC-U"

View File

@ -42,11 +42,12 @@ public:
virtual void SetBoolValue(const char* section, const char* key, bool value) = 0;
virtual void SetStringValue(const char* section, const char* key, const char* value) = 0;
virtual std::vector<std::string> GetStringList(const char* section, const char* key) = 0;
virtual std::vector<std::string> GetStringList(const char* section, const char* key) const = 0;
virtual void SetStringList(const char* section, const char* key, const std::vector<std::string>& items) = 0;
virtual bool RemoveFromStringList(const char* section, const char* key, const char* item) = 0;
virtual bool AddToStringList(const char* section, const char* key, const char* item) = 0;
virtual bool ContainsValue(const char* section, const char* key) const = 0;
virtual void DeleteValue(const char* section, const char* key) = 0;
virtual void ClearSection(const char* section) = 0;
@ -153,4 +154,67 @@ public:
{
value.has_value() ? SetStringValue(section, key, value.value()) : DeleteValue(section, key);
}
__fi void CopyBoolValue(const SettingsInterface& si, const char* section, const char* key)
{
bool value;
if (si.GetBoolValue(section, key, &value))
SetBoolValue(section, key, value);
else
DeleteValue(section, key);
}
__fi void CopyIntValue(const SettingsInterface& si, const char* section, const char* key)
{
int value;
if (si.GetIntValue(section, key, &value))
SetIntValue(section, key, value);
else
DeleteValue(section, key);
}
__fi void CopyUIntValue(const SettingsInterface& si, const char* section, const char* key)
{
uint value;
if (si.GetUIntValue(section, key, &value))
SetUIntValue(section, key, value);
else
DeleteValue(section, key);
}
__fi void CopyFloatValue(const SettingsInterface& si, const char* section, const char* key)
{
float value;
if (si.GetFloatValue(section, key, &value))
SetFloatValue(section, key, value);
else
DeleteValue(section, key);
}
__fi void CopyDoubleValue(const SettingsInterface& si, const char* section, const char* key)
{
double value;
if (si.GetDoubleValue(section, key, &value))
SetDoubleValue(section, key, value);
else
DeleteValue(section, key);
}
__fi void CopyStringValue(const SettingsInterface& si, const char* section, const char* key)
{
std::string value;
if (si.GetStringValue(section, key, &value))
SetStringValue(section, key, value.c_str());
else
DeleteValue(section, key);
}
__fi void CopyStringListValue(const SettingsInterface& si, const char* section, const char* key)
{
std::vector<std::string> value(si.GetStringList(section, key));
if (!value.empty())
SetStringList(section, key, value);
else
DeleteValue(section, key);
}
};

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,19 +18,44 @@
#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);
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 bindingSetupChanged(); });
updateSDLOptionsEnabled();
}

View File

@ -35,8 +35,12 @@ public:
void addDeviceToList(const QString& identifier, const QString& name);
void removeDeviceFromList(const QString& identifier);
Q_SIGNALS:
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,122 +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. (NOT YET IMPLEMENTED)</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="multitapPort1">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Multitap on Console Port 1</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="multitapPort2">
<property name="enabled">
<bool>false</bool>
</property>
<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,9 +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)
{
@ -32,21 +44,17 @@ ControllerSettingsDialog::ControllerSettingsDialog(QWidget* parent /* = nullptr
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->insertWidget(0, m_global_settings);
for (u32 i = 0; i < MAX_PORTS; i++)
{
m_port_bindings[i] = new ControllerBindingWidget(m_ui.settingsContainer, this, i);
m_ui.settingsContainer->insertWidget(i + 1, m_port_bindings[i]);
}
m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this);
m_ui.settingsContainer->insertWidget(3, m_hotkey_settings);
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);
@ -87,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;
@ -128,3 +253,213 @@ void ControllerSettingsDialog::onVibrationMotorsEnumerated(const QList<InputBind
m_vibration_motors.push_back(QString::fromStdString(key_str));
}
}
bool ControllerSettingsDialog::getBoolValue(const char* section, const char* key, bool default_value) const
{
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)
{
m_profile_interface->SetBoolValue(section, key, value);
m_profile_interface->Save();
g_emu_thread->reloadGameSettings();
}
else
{
QtHost::SetBaseBoolSettingValue(section, key, value);
g_emu_thread->applySettings();
}
}
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);
widget->deleteLater();
}
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 = {{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<u32, MAX_PORTS> mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}};
// create the ports
for (u32 global_slot : mtap_port_order)
{
const bool is_mtap_port = sioPadIsMultitapSlot(global_slot);
const auto [port, slot] = sioConvertPadToPortAndSlot(global_slot);
if (is_mtap_port && !mtap_enabled[port])
continue;
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);
}
// 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
@ -42,29 +45,57 @@ public:
enum : u32
{
MAX_PORTS = 2
MAX_PORTS = 8
};
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 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;
@ -73,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,42 +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>Controller 1</string>
</property>
<property name="icon">
<iconset theme="gamepad-line">
<normaloff>.</normaloff>.</iconset>
</property>
</item>
<item>
<property name="text">
<string>Controller 2</string>
</property>
<property name="icon">
<iconset theme="gamepad-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">
@ -95,23 +59,74 @@
<height>620</height>
</size>
</property>
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_3"/>
<widget class="QWidget" name="page_4"/>
<widget class="QWidget" name="page_5"/>
<widget class="QWidget" name="page_6"/>
<widget class="QWidget" name="page_2"/>
</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

@ -185,6 +185,11 @@ void INISettingsInterface::SetStringValue(const char* section, const char* key,
m_ini.SetValue(section, key, value, nullptr, true);
}
bool INISettingsInterface::ContainsValue(const char* section, const char* key) const
{
return (m_ini.GetValue(section, key, nullptr) != nullptr);
}
void INISettingsInterface::DeleteValue(const char* section, const char* key)
{
m_dirty = true;
@ -198,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

@ -48,10 +48,11 @@ public:
void SetDoubleValue(const char* section, const char* key, double value) override;
void SetBoolValue(const char* section, const char* key, bool value) override;
void SetStringValue(const char* section, const char* key, const char* value) override;
bool ContainsValue(const char* section, const char* key) const override;
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,13 +808,7 @@ bool InputManager::DoEventHook(InputBindingKey key, float value)
// Binding Updater
// ------------------------------------------------------------------------
// TODO(Stenzek): Find a better place for this. Maybe in PAD?
static constexpr std::array<const char*, InputManager::MAX_PAD_NUMBER> s_default_pad_types = {{
"DualShock2", // Pad 1
"None" // Pad 2
}};
void InputManager::ReloadBindings(SettingsInterface& si)
void InputManager::ReloadBindings(SettingsInterface& si, SettingsInterface& binding_si)
{
PauseVibration();
@ -823,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);
for (u32 pad = 0; pad < MAX_PAD_NUMBER; pad++)
AddPadBindings(si, pad, s_default_pad_types[pad]);
// 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(binding_si, pad, PAD::GetDefaultPadType(pad));
}
// ------------------------------------------------------------------------

View File

@ -174,9 +174,6 @@ class InputSource;
namespace InputManager
{
/// Number of emulated pads. TODO: Multitap support.
static constexpr u32 MAX_PAD_NUMBER = 2;
/// Minimum interval between vibration updates when the effect is continuous.
static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms
@ -226,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

@ -147,6 +147,19 @@ void LayeredSettingsInterface::SetStringValue(const char* section, const char* k
pxFailRel("Attempt to call SetStringValue() on layered settings interface");
}
bool LayeredSettingsInterface::ContainsValue(const char* section, const char* key) const
{
for (u32 layer = FIRST_LAYER; layer <= LAST_LAYER; layer++)
{
if (SettingsInterface* sif = m_layers[layer]; sif != nullptr)
{
if (sif->ContainsValue(key, section))
return true;
}
}
return false;
}
void LayeredSettingsInterface::DeleteValue(const char* section, const char* key)
{
pxFailRel("Attempt to call DeleteValue() on layered settings interface");
@ -157,7 +170,7 @@ void LayeredSettingsInterface::ClearSection(const char* section)
pxFailRel("Attempt to call ClearSection() on layered settings interface");
}
std::vector<std::string> LayeredSettingsInterface::GetStringList(const char* section, const char* key)
std::vector<std::string> LayeredSettingsInterface::GetStringList(const char* section, const char* key) const
{
std::vector<std::string> ret;

View File

@ -52,10 +52,11 @@ public:
void SetDoubleValue(const char* section, const char* key, double value) override;
void SetBoolValue(const char* section, const char* key, bool value) override;
void SetStringValue(const char* section, const char* key, const char* value) override;
bool ContainsValue(const char* section, const char* key) const override;
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

@ -2109,8 +2109,6 @@ void GSState::ReadFIFO(u8* mem, int size)
void GSState::ReadLocalMemoryUnsync(u8* mem, int qwc, GIFRegBITBLTBUF BITBLTBUF, GIFRegTRXPOS TRXPOS, GIFRegTRXREG TRXREG)
{
const int sx = TRXPOS.SSAX;
const int sy = TRXPOS.SSAY;
const int w = TRXREG.RRW;
const int h = TRXREG.RRH;

View File

@ -2422,15 +2422,17 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
const bool blend_mix3 = !!(blend_flag & BLEND_MIX3);
bool blend_mix = (blend_mix1 || blend_mix2 || blend_mix3);
const bool one_barrier = m_conf.require_one_barrier || blend_ad_alpha_masked;
// Blend can be done on hw. As and F cases should be accurate.
// BLEND_C_CLR1 with Ad, BLEND_C_CLR3 Cs > 0.5f will require sw blend.
// BLEND_C_CLR1 with As/F, BLEND_C_CLR2_AF, BLEND_C_CLR2_AS can be done in hw.
const bool clr_blend = !!(blend_flag & (BLEND_C_CLR1 | BLEND_C_CLR2_AF | BLEND_C_CLR2_AS | BLEND_C_CLR3));
bool clr_blend1_2 = (blend_flag & (BLEND_C_CLR1 | BLEND_C_CLR2_AF | BLEND_C_CLR2_AS))
&& (m_conf.ps.blend_c != 1) // Make sure it isn't an Ad case
&& !m_env.PABE.PABE // No PABE as it will require sw blending.
&& (m_env.COLCLAMP.CLAMP) // Let's add a colclamp check too, hw blend will clamp to 0-1.
&& !(m_conf.require_one_barrier || m_conf.require_full_barrier); // Also don't run if there are barriers present.
&& (m_conf.ps.blend_c != 1) // Make sure it isn't an Ad case
&& !m_env.PABE.PABE // No PABE as it will require sw blending.
&& (m_env.COLCLAMP.CLAMP) // Let's add a colclamp check too, hw blend will clamp to 0-1.
&& !(one_barrier || m_conf.require_full_barrier); // Also don't run if there are barriers present.
// Warning no break on purpose
// Note: the [[fallthrough]] attribute tell compilers not to complain about not having breaks.
@ -2439,10 +2441,9 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
{
// Condition 1: Require full sw blend for full barrier.
// Condition 2: One barrier is already enabled, prims don't overlap so let's use sw blend instead.
const bool prefer_sw_blend = m_conf.require_full_barrier || (m_conf.require_one_barrier && m_prim_overlap == PRIM_OVERLAP_NO);
const bool prefer_sw_blend = m_conf.require_full_barrier || (one_barrier && m_prim_overlap == PRIM_OVERLAP_NO);
// SW Blend is (nearly) free. Let's use it.
const bool one_barrier = m_conf.require_one_barrier || blend_ad_alpha_masked;
const bool no_prim_overlap = features.framebuffer_fetch ? (m_vt.m_primclass == GS_SPRITE_CLASS) : (m_prim_overlap == PRIM_OVERLAP_NO);
const bool impossible_or_free_blend = (blend_flag & BLEND_A_MAX) // Impossible blending
|| blend_non_recursive // Free sw blending, doesn't require barriers or reading fb
@ -2565,7 +2566,7 @@ void GSRendererHW::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER, bool&
{
// If we have fbfetch, use software blending when we need the fb value for anything else.
// This saves outputting the second color when it's not needed.
if (m_conf.require_one_barrier || m_conf.require_full_barrier)
if (one_barrier || m_conf.require_full_barrier)
{
sw_blending = true;
color_dest_blend = false;

View File

@ -32,6 +32,12 @@ SettingsInterface* Host::GetSettingsInterface()
return &s_layered_settings_interface;
}
SettingsInterface* Host::GetSettingsInterfaceForBindings()
{
SettingsInterface* input_layer = s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_INPUT);
return input_layer ? input_layer : &s_layered_settings_interface;
}
std::string Host::GetBaseStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/)
{
std::unique_lock lock(s_settings_mutex);
@ -169,6 +175,16 @@ SettingsInterface* Host::Internal::GetBaseSettingsLayer()
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE);
}
SettingsInterface* Host::Internal::GetGameSettingsLayer()
{
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_GAME);
}
SettingsInterface* Host::Internal::GetInputSettingsLayer()
{
return s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_INPUT);
}
void Host::Internal::SetBaseSettingsLayer(SettingsInterface* sif)
{
pxAssertRel(s_layered_settings_interface.GetLayer(LayeredSettingsInterface::LAYER_BASE) == nullptr, "Base layer has not been set");

View File

@ -56,11 +56,21 @@ namespace Host
std::unique_lock<std::mutex> GetSettingsLock();
SettingsInterface* GetSettingsInterface();
/// Returns the settings interface that controller bindings should be loaded from.
/// If an input profile is being used, this will be the input layer, otherwise the layered interface.
SettingsInterface* GetSettingsInterfaceForBindings();
namespace Internal
{
/// Retrieves the base settings layer. Must call with lock held.
SettingsInterface* GetBaseSettingsLayer();
/// Retrieves the game settings layer, if present. Must call with lock held.
SettingsInterface* GetGameSettingsLayer();
/// Retrieves the input settings layer, if present. Must call with lock held.
SettingsInterface* GetInputSettingsLayer();
/// Sets the base settings layer. Should be called by the host at initialization time.
void SetBaseSettingsLayer(SettingsInterface* sif);

View File

@ -15,19 +15,8 @@
#pragma once
#include <stdio.h>
#include <assert.h>
#include <array>
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <mutex>
#include <queue>
#include "common/Pcsx2Defs.h"
static const u32 GAMEPAD_NUMBER = 2;
static const u32 MAX_KEYS = 25;
enum gamePadValues

View File

@ -16,12 +16,17 @@
#include "PrecompiledHeader.h"
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/Global.h"
#include <array>
using namespace PAD;
KeyStatus::KeyStatus()
{
Init();
for (u32 pad = 0; pad < GAMEPAD_NUMBER; pad++)
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
m_axis_scale[pad] = 1.0f;
}
@ -29,7 +34,7 @@ KeyStatus::KeyStatus()
void KeyStatus::Init()
{
for (u32 pad = 0; pad < GAMEPAD_NUMBER; pad++)
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
m_button[pad] = 0xFFFFFFFF;

View File

@ -15,46 +15,46 @@
#pragma once
#include "PAD/Host/Global.h"
#include "PAD/Host/PAD.h"
namespace PAD
{
enum class ControllerType : u8;
}
enum class ControllerType : u8;
class KeyStatus
{
private:
static constexpr u8 m_analog_released_val = 0x7F;
struct PADAnalog
class KeyStatus
{
u8 lx, ly;
u8 rx, ry;
private:
static constexpr u8 m_analog_released_val = 0x7F;
struct PADAnalog
{
u8 lx, ly;
u8 rx, ry;
};
PAD::ControllerType m_type[NUM_CONTROLLER_PORTS] = {};
u32 m_button[NUM_CONTROLLER_PORTS];
u8 m_button_pressure[NUM_CONTROLLER_PORTS][MAX_KEYS];
PADAnalog m_analog[NUM_CONTROLLER_PORTS];
float m_axis_scale[NUM_CONTROLLER_PORTS];
float m_vibration_scale[NUM_CONTROLLER_PORTS][2];
public:
KeyStatus();
void Init();
void Set(u32 pad, u32 index, float value);
__fi PAD::ControllerType GetType(u32 pad) { return m_type[pad]; }
__fi void SetType(u32 pad, PAD::ControllerType type) { m_type[pad] = type; }
__fi void SetAxisScale(u32 pad, float scale) { m_axis_scale[pad] = scale; }
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
u32 GetButtons(u32 pad);
u8 GetPressure(u32 pad, u32 index);
};
} // namespace PAD
PAD::ControllerType m_type[GAMEPAD_NUMBER] = {};
u32 m_button[GAMEPAD_NUMBER];
u8 m_button_pressure[GAMEPAD_NUMBER][MAX_KEYS];
PADAnalog m_analog[GAMEPAD_NUMBER];
float m_axis_scale[GAMEPAD_NUMBER];
float m_vibration_scale[GAMEPAD_NUMBER][2];
public:
KeyStatus();
void Init();
void Set(u32 pad, u32 index, float value);
__fi PAD::ControllerType GetType(u32 pad) { return m_type[pad]; }
__fi void SetType(u32 pad, PAD::ControllerType type) { m_type[pad] = type; }
__fi void SetAxisScale(u32 pad, float scale) { m_axis_scale[pad] = scale; }
__fi float GetVibrationScale(u32 pad, u32 motor) const { return m_vibration_scale[pad][motor]; }
__fi void SetVibrationScale(u32 pad, u32 motor, float scale) { m_vibration_scale[pad][motor] = scale; }
u32 GetButtons(u32 pad);
u8 GetPressure(u32 pad, u32 index);
};
extern KeyStatus g_key_status;
extern PAD::KeyStatus g_key_status;

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"
@ -26,11 +28,13 @@
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/StateManagement.h"
#include <array>
const u32 revision = 3;
const u32 build = 0; // increase that with each version
#define PAD_SAVE_STATE_VERSION ((revision << 8) | (build << 0))
KeyStatus g_key_status;
PAD::KeyStatus g_key_status;
namespace PAD
{
@ -47,7 +51,7 @@ namespace PAD
static void ApplyMacroButton(u32 pad, const MacroButton& mb);
static void UpdateMacroButtons();
static std::array<std::array<MacroButton, NUM_MACRO_BUTTONS_PER_CONTROLLER>, GAMEPAD_NUMBER> s_macro_buttons;
static std::array<std::array<MacroButton, NUM_MACRO_BUTTONS_PER_CONTROLLER>, NUM_CONTROLLER_PORTS> s_macro_buttons;
} // namespace PAD
s32 PADinit()
@ -182,8 +186,11 @@ 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 < GAMEPAD_NUMBER; i++)
for (u32 i = 0; i < NUM_CONTROLLER_PORTS; i++)
{
const std::string section(StringUtil::StdStringFromFormat("Pad%u", i + 1u));
const std::string type(si.GetStringValue(section.c_str(), "Type", GetDefaultPadType(i)));
@ -221,7 +228,7 @@ void PAD::SetDefaultConfig(SettingsInterface& si)
{
si.ClearSection("InputSources");
for (u32 i = 0; i < GAMEPAD_NUMBER; i++)
for (u32 i = 0; i < NUM_CONTROLLER_PORTS; i++)
si.ClearSection(StringUtil::StdStringFromFormat("Pad%u", i + 1).c_str());
si.ClearSection("Hotkeys");
@ -230,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.
@ -384,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);
@ -455,7 +520,7 @@ bool PAD::MapController(SettingsInterface& si, u32 controller,
void PAD::SetControllerState(u32 controller, u32 bind, float value)
{
if (controller >= GAMEPAD_NUMBER || bind >= MAX_KEYS)
if (controller >= NUM_CONTROLLER_PORTS || bind >= MAX_KEYS)
return;
g_key_status.Set(controller, bind, value);
@ -502,7 +567,7 @@ void PAD::LoadMacroButtonConfig(const SettingsInterface& si, u32 pad, const std:
void PAD::SetMacroButtonState(u32 pad, u32 index, bool state)
{
if (pad >= GAMEPAD_NUMBER || index >= NUM_MACRO_BUTTONS_PER_CONTROLLER)
if (pad >= NUM_CONTROLLER_PORTS || index >= NUM_MACRO_BUTTONS_PER_CONTROLLER)
return;
MacroButton& mb = s_macro_buttons[pad][index];
@ -518,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;
@ -527,7 +606,7 @@ void PAD::ApplyMacroButton(u32 pad, const MacroButton& mb)
void PAD::UpdateMacroButtons()
{
for (u32 pad = 0; pad < GAMEPAD_NUMBER; pad++)
for (u32 pad = 0; pad < NUM_CONTROLLER_PORTS; pad++)
{
for (u32 index = 0; index < NUM_MACRO_BUTTONS_PER_CONTROLLER; index++)
{

View File

@ -16,6 +16,7 @@
#pragma once
#include <string>
#include <tuple>
#include <utility>
#include <vector>
@ -80,6 +81,9 @@ namespace PAD
PAD::VibrationCapabilities vibration_caps;
};
/// Total number of pad ports, across both multitaps.
static constexpr u32 NUM_CONTROLLER_PORTS = 8;
/// Number of macro buttons per controller.
static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 4;
@ -95,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();
@ -120,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

@ -19,6 +19,7 @@
#include "PAD/Host/KeyStatus.h"
#include "PAD/Host/PAD.h"
#include "Frontend/InputManager.h"
#include "Sio.h"
template <class T>
static bool __fi test_bit(T& value, int bit)
@ -63,7 +64,7 @@ void QueryInfo::reset()
u8 QueryInfo::start_poll(int _port)
{
if (_port >= static_cast<int>(GAMEPAD_NUMBER))
if (_port >= 2)
{
reset();
return 0;
@ -72,7 +73,9 @@ u8 QueryInfo::start_poll(int _port)
port = _port;
slot = slots[port];
if (g_key_status.GetType(_port) == PAD::ControllerType::NotConnected)
const u32 ext_port = sioConvertPortAndSlotToPad(port, slot);
if (g_key_status.GetType(ext_port) == PAD::ControllerType::NotConnected)
{
queryDone = 1;
numBytes = 0;
@ -163,7 +166,7 @@ void Pad::rumble_all()
{
for (unsigned port = 0; port < 2; port++)
for (unsigned slot = 0; slot < 4; slot++)
pads[port][slot].rumble(port);
pads[port][slot].rumble(sioConvertPortAndSlotToPad(port, slot));
}
//////////////////////////////////////////////////////////////////////
@ -254,7 +257,8 @@ u8 pad_poll(u8 value)
b1=b1 & 0x1f;
#endif
uint32_t buttons = g_key_status.GetButtons(query.port);
const u32 ext_port = sioConvertPortAndSlotToPad(query.port, query.slot);
const u32 buttons = g_key_status.GetButtons(ext_port);
if (!test_bit(buttons, PAD_ANALOG) && !pad->modeLock)
{
switch (pad->mode)
@ -279,28 +283,28 @@ u8 pad_poll(u8 value)
{ // ANALOG || DS2 native
query.numBytes = 9;
query.response[5] = g_key_status.GetPressure(query.port, PAD_R_RIGHT);
query.response[6] = g_key_status.GetPressure(query.port, PAD_R_UP);
query.response[7] = g_key_status.GetPressure(query.port, PAD_L_RIGHT);
query.response[8] = g_key_status.GetPressure(query.port, PAD_L_UP);
query.response[5] = g_key_status.GetPressure(ext_port, PAD_R_RIGHT);
query.response[6] = g_key_status.GetPressure(ext_port, PAD_R_UP);
query.response[7] = g_key_status.GetPressure(ext_port, PAD_L_RIGHT);
query.response[8] = g_key_status.GetPressure(ext_port, PAD_L_UP);
if (pad->mode != MODE_ANALOG)
{ // DS2 native
query.numBytes = 21;
query.response[9] = !test_bit(buttons, 13) ? g_key_status.GetPressure(query.port, PAD_RIGHT) : 0;
query.response[10] = !test_bit(buttons, 15) ? g_key_status.GetPressure(query.port, PAD_LEFT) : 0;
query.response[11] = !test_bit(buttons, 12) ? g_key_status.GetPressure(query.port, PAD_UP) : 0;
query.response[12] = !test_bit(buttons, 14) ? g_key_status.GetPressure(query.port, PAD_DOWN) : 0;
query.response[9] = !test_bit(buttons, 13) ? g_key_status.GetPressure(ext_port, PAD_RIGHT) : 0;
query.response[10] = !test_bit(buttons, 15) ? g_key_status.GetPressure(ext_port, PAD_LEFT) : 0;
query.response[11] = !test_bit(buttons, 12) ? g_key_status.GetPressure(ext_port, PAD_UP) : 0;
query.response[12] = !test_bit(buttons, 14) ? g_key_status.GetPressure(ext_port, PAD_DOWN) : 0;
query.response[13] = !test_bit(buttons, 4) ? g_key_status.GetPressure(query.port, PAD_TRIANGLE) : 0;
query.response[14] = !test_bit(buttons, 5) ? g_key_status.GetPressure(query.port, PAD_CIRCLE) : 0;
query.response[15] = !test_bit(buttons, 6) ? g_key_status.GetPressure(query.port, PAD_CROSS) : 0;
query.response[16] = !test_bit(buttons, 7) ? g_key_status.GetPressure(query.port, PAD_SQUARE) : 0;
query.response[17] = !test_bit(buttons, 2) ? g_key_status.GetPressure(query.port, PAD_L1) : 0;
query.response[18] = !test_bit(buttons, 3) ? g_key_status.GetPressure(query.port, PAD_R1) : 0;
query.response[19] = !test_bit(buttons, 0) ? g_key_status.GetPressure(query.port, PAD_L2) : 0;
query.response[20] = !test_bit(buttons, 1) ? g_key_status.GetPressure(query.port, PAD_R2) : 0;
query.response[13] = !test_bit(buttons, 4) ? g_key_status.GetPressure(ext_port, PAD_TRIANGLE) : 0;
query.response[14] = !test_bit(buttons, 5) ? g_key_status.GetPressure(ext_port, PAD_CIRCLE) : 0;
query.response[15] = !test_bit(buttons, 6) ? g_key_status.GetPressure(ext_port, PAD_CROSS) : 0;
query.response[16] = !test_bit(buttons, 7) ? g_key_status.GetPressure(ext_port, PAD_SQUARE) : 0;
query.response[17] = !test_bit(buttons, 2) ? g_key_status.GetPressure(ext_port, PAD_L1) : 0;
query.response[18] = !test_bit(buttons, 3) ? g_key_status.GetPressure(ext_port, PAD_R1) : 0;
query.response[19] = !test_bit(buttons, 0) ? g_key_status.GetPressure(ext_port, PAD_L2) : 0;
query.response[20] = !test_bit(buttons, 1) ? g_key_status.GetPressure(ext_port, PAD_R2) : 0;
}
}

View File

@ -1069,8 +1069,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

@ -1334,3 +1334,33 @@ void SaveStateBase::sioFreeze()
}
}
}
std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index)
{
if (index > 4) // [5,6,7]
return std::make_tuple(1, index - 4); // 2B,2C,2D
else if (index > 1) // [2,3,4]
return std::make_tuple(0, index - 1); // 1B,1C,1D
else // [0,1]
return std::make_tuple(index, 0); // 1A,2A
}
u32 sioConvertPortAndSlotToPad(u32 port, u32 slot)
{
if (slot == 0)
return port;
else if (port == 0) // slot=[0,1]
return slot + 1; // 2,3,4
else
return slot + 4; // 5,6,7
}
bool sioPadIsMultitapSlot(u32 index)
{
return (index >= 2);
}
bool sioPortAndSlotIsMultitap(u32 port, u32 slot)
{
return (slot != 0);
}

View File

@ -127,3 +127,13 @@ extern void ClearMcdEjectTimeoutNow();
extern void sioStatRead();
extern void sioSetGameSerial(const std::string& serial);
extern void sioNextFrame();
/// Converts a global pad index to a multitap port and slot.
extern std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index);
/// Converts a multitap port and slot to a global pad index.
extern u32 sioConvertPortAndSlotToPad(u32 port, u32 slot);
/// Returns true if the given pad index is a multitap slot.
extern bool sioPadIsMultitapSlot(u32 index);
extern bool sioPortAndSlotIsMultitap(u32 port, u32 slot);

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;

View File

@ -141,7 +141,7 @@ void wxSettingsInterface::SetStringValue(const char* section, const char* key, c
m_config->Write(key, wxString::FromUTF8(value));
}
std::vector<std::string> wxSettingsInterface::GetStringList(const char* section, const char* key)
std::vector<std::string> wxSettingsInterface::GetStringList(const char* section, const char* key) const
{
pxFailRel("Not implemented");
return {};
@ -163,6 +163,12 @@ bool wxSettingsInterface::AddToStringList(const char* section, const char* key,
return false;
}
bool wxSettingsInterface::ContainsValue(const char* section, const char* key) const
{
CheckPath(section);
return m_config->Exists(key);
}
void wxSettingsInterface::DeleteValue(const char* section, const char* key)
{
CheckPath(section);

View File

@ -44,11 +44,12 @@ public:
void SetStringValue(const char* section, const char* key, const char* value) 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;
bool ContainsValue(const char* section, const char* key) const override;
void DeleteValue(const char* section, const char* key) override;
void ClearSection(const char* section) override;