mirror of
https://github.com/stenzek/duckstation.git
synced 2025-06-08 04:25:37 +00:00
Qt: Add 'Multiple Devices' to automatic mapping
Also populate the "current device" label with the device from the config when running the setup wizard, instead of always setting the label to Keyboard.
This commit is contained in:
parent
b08ab9f712
commit
9113a6e6a6
@ -3236,7 +3236,7 @@ void FullscreenUI::StartAutomaticBindingForPort(u32 port)
|
|||||||
auto lock = Host::GetSettingsLock();
|
auto lock = Host::GetSettingsLock();
|
||||||
SettingsInterface* bsi = GetEditingSettingsInterface();
|
SettingsInterface* bsi = GetEditingSettingsInterface();
|
||||||
const bool result =
|
const bool result =
|
||||||
InputManager::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name));
|
InputManager::MapController(*bsi, port, InputManager::GetGenericBindingMapping(name), true);
|
||||||
SetSettingsChanged(bsi);
|
SetSettingsChanged(bsi);
|
||||||
|
|
||||||
// and the toast needs to happen on the UI thread.
|
// and the toast needs to happen on the UI thread.
|
||||||
|
@ -1187,7 +1187,7 @@ void Settings::SetDefaultControllerConfig(SettingsInterface& si)
|
|||||||
|
|
||||||
#ifndef __ANDROID__
|
#ifndef __ANDROID__
|
||||||
// Use the automapper to set this up.
|
// Use the automapper to set this up.
|
||||||
InputManager::MapController(si, 0, InputManager::GetGenericBindingMapping("Keyboard"));
|
InputManager::MapController(si, 0, InputManager::GetGenericBindingMapping("Keyboard"), true);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,12 @@ void ControllerBindingWidget::onAutomaticBindingClicked()
|
|||||||
added = true;
|
added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!added)
|
if (added)
|
||||||
|
{
|
||||||
|
QAction* action = menu.addAction(tr("Multiple devices..."));
|
||||||
|
connect(action, &QAction::triggered, this, &ControllerBindingWidget::onMultipleDeviceAutomaticBindingTriggered);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
QAction* action = menu.addAction(tr("No devices available"));
|
QAction* action = menu.addAction(tr("No devices available"));
|
||||||
action->setEnabled(false);
|
action->setEnabled(false);
|
||||||
@ -346,11 +351,11 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
|
|||||||
if (m_dialog->isEditingGlobalSettings())
|
if (m_dialog->isEditingGlobalSettings())
|
||||||
{
|
{
|
||||||
auto lock = Host::GetSettingsLock();
|
auto lock = Host::GetSettingsLock();
|
||||||
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping);
|
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), m_port_number, mapping, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = InputManager::MapController(*m_dialog->getEditingSettingsInterface(), m_port_number, mapping);
|
result = InputManager::MapController(*m_dialog->getEditingSettingsInterface(), m_port_number, mapping, true);
|
||||||
QtHost::SaveGameSettings(m_dialog->getEditingSettingsInterface(), false);
|
QtHost::SaveGameSettings(m_dialog->getEditingSettingsInterface(), false);
|
||||||
g_emu_thread->reloadInputBindings();
|
g_emu_thread->reloadInputBindings();
|
||||||
}
|
}
|
||||||
@ -360,6 +365,104 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device)
|
|||||||
saveAndRefresh();
|
saveAndRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControllerBindingWidget::onMultipleDeviceAutomaticBindingTriggered()
|
||||||
|
{
|
||||||
|
// force a refresh after mapping
|
||||||
|
if (doMultipleDeviceAutomaticBinding(this, m_dialog, m_port_number))
|
||||||
|
onTypeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ControllerBindingWidget::doMultipleDeviceAutomaticBinding(QWidget* parent, ControllerSettingsWindow* parent_dialog,
|
||||||
|
u32 port)
|
||||||
|
{
|
||||||
|
QDialog dialog(parent);
|
||||||
|
|
||||||
|
QVBoxLayout* layout = new QVBoxLayout(&dialog);
|
||||||
|
QLabel help(tr("Select the devices from the list below that you want to bind to this controller."), &dialog);
|
||||||
|
layout->addWidget(&help);
|
||||||
|
|
||||||
|
QListWidget list(&dialog);
|
||||||
|
list.setSelectionMode(QListWidget::SingleSelection);
|
||||||
|
layout->addWidget(&list);
|
||||||
|
|
||||||
|
for (const InputDeviceListModel::Device& dev : g_emu_thread->getInputDeviceListModel()->getDeviceList())
|
||||||
|
{
|
||||||
|
QListWidgetItem* item = new QListWidgetItem;
|
||||||
|
item->setText(QStringLiteral("%1 (%2)").arg(dev.identifier).arg(dev.display_name));
|
||||||
|
item->setData(Qt::UserRole, dev.identifier);
|
||||||
|
item->setIcon(InputDeviceListModel::getIconForKey(dev.key));
|
||||||
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||||
|
item->setCheckState(Qt::Unchecked);
|
||||||
|
list.addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialogButtonBox bb(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
|
||||||
|
connect(&bb, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||||
|
connect(&bb, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||||
|
layout->addWidget(&bb);
|
||||||
|
|
||||||
|
if (dialog.exec() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto lock = Host::GetSettingsLock();
|
||||||
|
const bool global = (!parent_dialog || parent_dialog->isEditingGlobalSettings());
|
||||||
|
SettingsInterface& si =
|
||||||
|
*(global ? Host::Internal::GetBaseSettingsLayer() : parent_dialog->getEditingSettingsInterface());
|
||||||
|
|
||||||
|
// first device should clear mappings
|
||||||
|
bool tried_any = false;
|
||||||
|
bool mapped_any = false;
|
||||||
|
const int count = list.count();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
QListWidgetItem* item = list.item(i);
|
||||||
|
if (item->checkState() != Qt::Checked)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tried_any = true;
|
||||||
|
|
||||||
|
const QString identifier = item->data(Qt::UserRole).toString();
|
||||||
|
std::vector<std::pair<GenericInputBinding, std::string>> mapping =
|
||||||
|
InputManager::GetGenericBindingMapping(identifier.toStdString());
|
||||||
|
if (mapping.empty())
|
||||||
|
{
|
||||||
|
lock.unlock();
|
||||||
|
QMessageBox::critical(QtUtils::GetRootWidget(parent), tr("Automatic Mapping"),
|
||||||
|
tr("No generic bindings were generated for device '%1'. The controller/source may not "
|
||||||
|
"support automatic mapping.")
|
||||||
|
.arg(identifier));
|
||||||
|
lock.lock();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_any |= InputManager::MapController(si, port, mapping, !mapped_any);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
if (!tried_any)
|
||||||
|
{
|
||||||
|
QMessageBox::information(QtUtils::GetRootWidget(parent), tr("Automatic Mapping"), tr("No devices were selected."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapped_any)
|
||||||
|
{
|
||||||
|
if (global)
|
||||||
|
{
|
||||||
|
QtHost::SaveGameSettings(&si, false);
|
||||||
|
g_emu_thread->reloadGameSettings(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QtHost::QueueSettingsSave();
|
||||||
|
g_emu_thread->reloadInputBindings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped_any;
|
||||||
|
}
|
||||||
|
|
||||||
void ControllerBindingWidget::saveAndRefresh()
|
void ControllerBindingWidget::saveAndRefresh()
|
||||||
{
|
{
|
||||||
onTypeChanged();
|
onTypeChanged();
|
||||||
|
@ -37,6 +37,8 @@ public:
|
|||||||
ALWAYS_INLINE u32 getPortNumber() const { return m_port_number; }
|
ALWAYS_INLINE u32 getPortNumber() const { return m_port_number; }
|
||||||
ALWAYS_INLINE const QIcon& getIcon() { return m_icon; }
|
ALWAYS_INLINE const QIcon& getIcon() { return m_icon; }
|
||||||
|
|
||||||
|
static bool doMultipleDeviceAutomaticBinding(QWidget* parent, ControllerSettingsWindow* parent_dialog, u32 port);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onTypeChanged();
|
void onTypeChanged();
|
||||||
void onAutomaticBindingClicked();
|
void onAutomaticBindingClicked();
|
||||||
@ -44,6 +46,7 @@ private Q_SLOTS:
|
|||||||
void onBindingsClicked();
|
void onBindingsClicked();
|
||||||
void onSettingsClicked();
|
void onSettingsClicked();
|
||||||
void onMacrosClicked();
|
void onMacrosClicked();
|
||||||
|
void onMultipleDeviceAutomaticBindingTriggered();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void populateControllerTypes();
|
void populateControllerTypes();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
#include "setupwizarddialog.h"
|
#include "setupwizarddialog.h"
|
||||||
|
#include "controllerbindingwidgets.h"
|
||||||
#include "controllersettingwidgetbinder.h"
|
#include "controllersettingwidgetbinder.h"
|
||||||
#include "interfacesettingswidget.h"
|
#include "interfacesettingswidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
@ -411,7 +412,7 @@ void SetupWizardDialog::setupControllerPage(bool initial)
|
|||||||
nullptr, w.type_combo, section, "Type",
|
nullptr, w.type_combo, section, "Type",
|
||||||
Controller::GetControllerInfo(Settings::GetDefaultControllerType(port)).name);
|
Controller::GetControllerInfo(Settings::GetDefaultControllerType(port)).name);
|
||||||
|
|
||||||
w.mapping_result->setText((port == 0) ? tr("Default (Keyboard)") : tr("Default (None)"));
|
w.mapping_result->setText(findCurrentDeviceForPort(port));
|
||||||
|
|
||||||
if (initial)
|
if (initial)
|
||||||
{
|
{
|
||||||
@ -431,6 +432,13 @@ void SetupWizardDialog::updateStylesheets()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SetupWizardDialog::findCurrentDeviceForPort(u32 port) const
|
||||||
|
{
|
||||||
|
auto lock = Host::GetSettingsLock();
|
||||||
|
return QString::fromStdString(
|
||||||
|
InputManager::GetPhysicalDeviceForController(*Host::Internal::GetBaseSettingsLayer(), port));
|
||||||
|
}
|
||||||
|
|
||||||
void SetupWizardDialog::openAutomaticMappingMenu(u32 port, QLabel* update_label)
|
void SetupWizardDialog::openAutomaticMappingMenu(u32 port, QLabel* update_label)
|
||||||
{
|
{
|
||||||
QMenu menu(this);
|
QMenu menu(this);
|
||||||
@ -448,7 +456,13 @@ void SetupWizardDialog::openAutomaticMappingMenu(u32 port, QLabel* update_label)
|
|||||||
added = true;
|
added = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!added)
|
if (added)
|
||||||
|
{
|
||||||
|
QAction* action = menu.addAction(tr("Multiple Devices..."));
|
||||||
|
connect(action, &QAction::triggered, this,
|
||||||
|
[this, port, update_label]() { doMultipleDeviceAutomaticBinding(port, update_label); });
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
QAction* action = menu.addAction(tr("No devices available"));
|
QAction* action = menu.addAction(tr("No devices available"));
|
||||||
action->setEnabled(false);
|
action->setEnabled(false);
|
||||||
@ -474,7 +488,7 @@ void SetupWizardDialog::doDeviceAutomaticBinding(u32 port, QLabel* update_label,
|
|||||||
bool result;
|
bool result;
|
||||||
{
|
{
|
||||||
auto lock = Host::GetSettingsLock();
|
auto lock = Host::GetSettingsLock();
|
||||||
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), port, mapping);
|
result = InputManager::MapController(*Host::Internal::GetBaseSettingsLayer(), port, mapping, true);
|
||||||
}
|
}
|
||||||
if (!result)
|
if (!result)
|
||||||
return;
|
return;
|
||||||
@ -483,3 +497,11 @@ void SetupWizardDialog::doDeviceAutomaticBinding(u32 port, QLabel* update_label,
|
|||||||
|
|
||||||
update_label->setText(device);
|
update_label->setText(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetupWizardDialog::doMultipleDeviceAutomaticBinding(u32 port, QLabel* update_label)
|
||||||
|
{
|
||||||
|
if (!ControllerBindingWidget::doMultipleDeviceAutomaticBinding(this, nullptr, port))
|
||||||
|
return;
|
||||||
|
|
||||||
|
update_label->setText(findCurrentDeviceForPort(port));
|
||||||
|
}
|
||||||
|
@ -45,6 +45,8 @@ private Q_SLOTS:
|
|||||||
void refreshDirectoryList();
|
void refreshDirectoryList();
|
||||||
void resizeDirectoryListColumns();
|
void resizeDirectoryListColumns();
|
||||||
|
|
||||||
|
void doMultipleDeviceAutomaticBinding(u32 port, QLabel* update_label);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent* event);
|
void resizeEvent(QResizeEvent* event);
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ private:
|
|||||||
|
|
||||||
void addPathToTable(const std::string& path, bool recursive);
|
void addPathToTable(const std::string& path, bool recursive);
|
||||||
|
|
||||||
|
QString findCurrentDeviceForPort(u32 port) const;
|
||||||
void openAutomaticMappingMenu(u32 port, QLabel* update_label);
|
void openAutomaticMappingMenu(u32 port, QLabel* update_label);
|
||||||
void doDeviceAutomaticBinding(u32 port, QLabel* update_label, const QString& device);
|
void doDeviceAutomaticBinding(u32 port, QLabel* update_label, const QString& device);
|
||||||
|
|
||||||
|
@ -1530,7 +1530,7 @@ void InputManager::CopyConfiguration(SettingsInterface* dest_si, const SettingsI
|
|||||||
|
|
||||||
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
|
static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& section,
|
||||||
const GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
|
const GenericInputBindingMapping& mapping, GenericInputBinding generic_name,
|
||||||
const char* bind_name)
|
const char* bind_name, bool clear_existing_mappings)
|
||||||
{
|
{
|
||||||
// find the mapping it corresponds to
|
// find the mapping it corresponds to
|
||||||
const std::string* found_mapping = nullptr;
|
const std::string* found_mapping = nullptr;
|
||||||
@ -1546,18 +1546,25 @@ static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& sectio
|
|||||||
if (found_mapping)
|
if (found_mapping)
|
||||||
{
|
{
|
||||||
INFO_LOG("Map {}/{} to '{}'", section, bind_name, *found_mapping);
|
INFO_LOG("Map {}/{} to '{}'", section, bind_name, *found_mapping);
|
||||||
|
if (clear_existing_mappings)
|
||||||
si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
|
si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
|
||||||
|
else
|
||||||
|
si.AddToStringList(section.c_str(), bind_name, found_mapping->c_str());
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (clear_existing_mappings)
|
||||||
si.DeleteValue(section.c_str(), bind_name);
|
si.DeleteValue(section.c_str(), bind_name);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputManager::MapController(SettingsInterface& si, u32 controller,
|
bool InputManager::MapController(SettingsInterface& si, u32 controller,
|
||||||
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping)
|
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping,
|
||||||
|
bool clear_existing_mappings)
|
||||||
{
|
{
|
||||||
const std::string section = Controller::GetSettingsSection(controller);
|
const std::string section = Controller::GetSettingsSection(controller);
|
||||||
const TinyString type = si.GetTinyStringValue(
|
const TinyString type = si.GetTinyStringValue(
|
||||||
@ -1572,11 +1579,15 @@ bool InputManager::MapController(SettingsInterface& si, u32 controller,
|
|||||||
if (bi.generic_mapping == GenericInputBinding::Unknown)
|
if (bi.generic_mapping == GenericInputBinding::Unknown)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
u32 mappings_added = TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name);
|
u32 mappings_added =
|
||||||
|
TryMapGenericMapping(si, section, mapping, bi.generic_mapping, bi.name, clear_existing_mappings);
|
||||||
|
|
||||||
// try to map to small motor if we tried big motor
|
// try to map to small motor if we tried big motor
|
||||||
if (mappings_added == 0 && bi.generic_mapping == GenericInputBinding::LargeMotor)
|
if (mappings_added == 0 && bi.generic_mapping == GenericInputBinding::LargeMotor)
|
||||||
mappings_added += TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, bi.name);
|
{
|
||||||
|
mappings_added +=
|
||||||
|
TryMapGenericMapping(si, section, mapping, GenericInputBinding::SmallMotor, bi.name, clear_existing_mappings);
|
||||||
|
}
|
||||||
|
|
||||||
num_mappings += mappings_added;
|
num_mappings += mappings_added;
|
||||||
}
|
}
|
||||||
@ -1584,6 +1595,45 @@ bool InputManager::MapController(SettingsInterface& si, u32 controller,
|
|||||||
return (num_mappings > 0);
|
return (num_mappings > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string InputManager::GetPhysicalDeviceForController(SettingsInterface& si, u32 controller)
|
||||||
|
{
|
||||||
|
std::string ret;
|
||||||
|
|
||||||
|
const std::string section = Controller::GetSettingsSection(controller);
|
||||||
|
const TinyString type = si.GetTinyStringValue(
|
||||||
|
section.c_str(), "Type", Controller::GetControllerInfo(Settings::GetDefaultControllerType(controller)).name);
|
||||||
|
const Controller::ControllerInfo* info = Controller::GetControllerInfo(type);
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
for (const Controller::ControllerBindingInfo& bi : info->bindings)
|
||||||
|
{
|
||||||
|
for (const std::string& binding : si.GetStringList(section.c_str(), bi.name))
|
||||||
|
{
|
||||||
|
std::string_view source, sub_binding;
|
||||||
|
if (!SplitBinding(binding, &source, &sub_binding))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ret.empty())
|
||||||
|
{
|
||||||
|
ret = source;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != source)
|
||||||
|
{
|
||||||
|
ret = TRANSLATE_STR("InputManager", "Multiple Devices");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret.empty())
|
||||||
|
ret = TRANSLATE_STR("InputManager", "None");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> InputManager::GetInputProfileNames()
|
std::vector<std::string> InputManager::GetInputProfileNames()
|
||||||
{
|
{
|
||||||
FileSystem::FindResultsArray results;
|
FileSystem::FindResultsArray results;
|
||||||
|
@ -384,7 +384,11 @@ void CopyConfiguration(SettingsInterface* dest_si, const SettingsInterface& src_
|
|||||||
|
|
||||||
/// Performs automatic controller mapping with the provided list of generic mappings.
|
/// Performs automatic controller mapping with the provided list of generic mappings.
|
||||||
bool MapController(SettingsInterface& si, u32 controller,
|
bool MapController(SettingsInterface& si, u32 controller,
|
||||||
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping);
|
const std::vector<std::pair<GenericInputBinding, std::string>>& mapping,
|
||||||
|
bool clear_existing_mappings);
|
||||||
|
|
||||||
|
/// Returns the name of the first physical device mapped to the emulated controller, "None", or "Multiple Devices".
|
||||||
|
std::string GetPhysicalDeviceForController(SettingsInterface& si, u32 controller);
|
||||||
|
|
||||||
/// Returns a list of input profiles available.
|
/// Returns a list of input profiles available.
|
||||||
std::vector<std::string> GetInputProfileNames();
|
std::vector<std::string> GetInputProfileNames();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user