Qt: Refactor input device/motor tracking

Remove multiple sources of truth.
This commit is contained in:
Stenzek 2025-01-11 17:34:55 +10:00
parent 844287b722
commit 13b85728a0
No known key found for this signature in database
25 changed files with 292 additions and 277 deletions

View File

@ -2850,7 +2850,7 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title,
void FullscreenUI::StartAutomaticBinding(u32 port)
{
std::vector<std::pair<std::string, std::string>> devices(InputManager::EnumerateDevices());
InputManager::DeviceList devices = InputManager::EnumerateDevices();
if (devices.empty())
{
ShowToast({}, FSUI_STR("Automatic mapping failed, no devices are available."));
@ -2861,7 +2861,7 @@ void FullscreenUI::StartAutomaticBinding(u32 port)
ImGuiFullscreen::ChoiceDialogOptions options;
options.reserve(devices.size());
names.reserve(devices.size());
for (auto& [name, display_name] : devices)
for (auto& [key, name, display_name] : devices)
{
names.push_back(std::move(name));
options.emplace_back(std::move(display_name), false);

View File

@ -272,16 +272,11 @@ void ControllerBindingWidget::onAutomaticBindingClicked()
QMenu menu(this);
bool added = false;
const auto& device_list = m_dialog->isEditingGameSettings() ?
g_main_window->getControllerSettingsWindow()->getDeviceList() :
m_dialog->getDeviceList();
for (const auto& [identifier, device_name] : device_list)
for (const auto& [identifier, device_name] : g_emu_thread->getInputDeviceListModel()->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
const QString qidentifier = QString::fromStdString(identifier);
QAction* action =
menu.addAction(QStringLiteral("%1 (%2)").arg(qidentifier).arg(QString::fromStdString(device_name)));
action->setData(qidentifier);
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(identifier).arg(device_name));
action->setData(identifier);
connect(action, &QAction::triggered, this,
[this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;

View File

@ -87,14 +87,7 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
m_ui.profileSettings = nullptr;
}
if (dialog->isEditingGameSettings())
{
m_ui.mainLayout->removeWidget(m_ui.deviceListGroup);
delete m_ui.deviceList;
m_ui.deviceList = nullptr;
delete m_ui.deviceListGroup;
m_ui.deviceListGroup = nullptr;
}
m_ui.deviceList->setModel(g_emu_thread->getInputDeviceListModel());
connect(m_ui.multitapMode, &QComboBox::currentIndexChanged, this, [this]() { emit bindingSetupChanged(); });
@ -110,28 +103,6 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
ControllerGlobalSettingsWidget::~ControllerGlobalSettingsWidget() = default;
void ControllerGlobalSettingsWidget::addDeviceToList(const QString& identifier, const QString& name)
{
QListWidgetItem* item = new QListWidgetItem();
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
item->setData(Qt::UserRole, identifier);
m_ui.deviceList->addItem(item);
}
void ControllerGlobalSettingsWidget::removeDeviceFromList(const QString& identifier)
{
const int count = m_ui.deviceList->count();
for (int i = 0; i < count; i++)
{
QListWidgetItem* item = m_ui.deviceList->item(i);
if (item->data(Qt::UserRole) != identifier)
continue;
delete m_ui.deviceList->takeItem(i);
break;
}
}
void ControllerGlobalSettingsWidget::ledSettingsClicked()
{
ControllerLEDSettingsDialog dialog(this, m_dialog);

View File

@ -25,9 +25,6 @@ public:
ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsWindow* dialog);
~ControllerGlobalSettingsWidget();
void addDeviceToList(const QString& identifier, const QString& name);
void removeDeviceFromList(const QString& identifier);
Q_SIGNALS:
void bindingSetupChanged();

View File

@ -56,7 +56,7 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QListWidget" name="deviceList">
<widget class="QListView" name="deviceList">
<property name="minimumSize">
<size>
<width>200</width>

View File

@ -49,18 +49,6 @@ ControllerSettingsWindow::ControllerSettingsWindow(SettingsInterface* game_sif /
connect(m_ui.applyProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onApplyProfileClicked);
connect(m_ui.deleteProfile, &QPushButton::clicked, this, &ControllerSettingsWindow::onDeleteProfileClicked);
connect(m_ui.restoreDefaults, &QPushButton::clicked, this, &ControllerSettingsWindow::onRestoreDefaultsClicked);
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this,
&ControllerSettingsWindow::onInputDevicesEnumerated);
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsWindow::onInputDeviceConnected);
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this,
&ControllerSettingsWindow::onInputDeviceDisconnected);
connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this,
&ControllerSettingsWindow::onVibrationMotorsEnumerated);
// trigger a device enumeration to populate the device list
g_emu_thread->enumerateInputDevices();
g_emu_thread->enumerateVibrationMotors();
}
else
{
@ -324,48 +312,6 @@ void ControllerSettingsWindow::onRestoreDefaultsForGameClicked()
tr("Per-game controller configuration reset to default settings."));
}
void ControllerSettingsWindow::onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices)
{
m_device_list = devices;
for (const auto& [device_name, display_name] : m_device_list)
m_global_settings->addDeviceToList(QString::fromStdString(device_name), QString::fromStdString(display_name));
}
void ControllerSettingsWindow::onInputDeviceConnected(const std::string& identifier, const std::string& device_name)
{
m_device_list.emplace_back(identifier, device_name);
m_global_settings->addDeviceToList(QString::fromStdString(identifier), QString::fromStdString(device_name));
g_emu_thread->enumerateVibrationMotors();
}
void ControllerSettingsWindow::onInputDeviceDisconnected(const std::string& identifier)
{
for (auto iter = m_device_list.begin(); iter != m_device_list.end(); ++iter)
{
if (iter->first == identifier)
{
m_device_list.erase(iter);
break;
}
}
m_global_settings->removeDeviceFromList(QString::fromStdString(identifier));
g_emu_thread->enumerateVibrationMotors();
}
void ControllerSettingsWindow::onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors)
{
m_vibration_motors.clear();
m_vibration_motors.reserve(motors.size());
for (const InputBindingKey key : motors)
{
const std::string key_str(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, key));
if (!key_str.empty())
m_vibration_motors.push_back(QString::fromStdString(key_str));
}
}
bool ControllerSettingsWindow::getBoolValue(const char* section, const char* key, bool default_value) const
{
if (m_editing_settings_interface)
@ -489,11 +435,6 @@ void ControllerSettingsWindow::createWidgets()
m_ui.settingsContainer->addWidget(m_global_settings);
connect(m_global_settings, &ControllerGlobalSettingsWidget::bindingSetupChanged, this,
&ControllerSettingsWindow::createWidgets);
if (!isEditingGameSettings())
{
for (const auto& [identifier, device_name] : m_device_list)
m_global_settings->addDeviceToList(QString::fromStdString(identifier), QString::fromStdString(device_name));
}
}
// load mtap settings

View File

@ -48,9 +48,6 @@ public:
ALWAYS_INLINE HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; }
ALWAYS_INLINE const std::vector<std::pair<std::string, std::string>>& getDeviceList() const { return m_device_list; }
ALWAYS_INLINE const QStringList& getVibrationMotors() const { return m_vibration_motors; }
ALWAYS_INLINE bool isEditingGlobalSettings() const
{
return (m_profile_name.isEmpty() && !m_editing_settings_interface);
@ -95,11 +92,6 @@ private Q_SLOTS:
void onCopyGlobalSettingsClicked();
void onRestoreDefaultsForGameClicked();
void onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices);
void onInputDeviceConnected(const std::string& identifier, const std::string& device_name);
void onInputDeviceDisconnected(const std::string& identifier);
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
void createWidgets();
protected:
@ -119,9 +111,6 @@ private:
std::array<ControllerBindingWidget*, NUM_CONTROLLER_AND_CARD_PORTS> m_port_bindings{};
HotkeySettingsWidget* m_hotkey_settings = nullptr;
std::vector<std::pair<std::string, std::string>> m_device_list;
QStringList m_vibration_motors;
QString m_profile_name;
std::unique_ptr<SettingsInterface> m_profile_settings_interface;
};

View File

@ -448,7 +448,7 @@ void InputVibrationBindingWidget::onClicked()
const QString full_key(
QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name)));
const QString current(QString::fromStdString(m_binding));
QStringList input_options(m_dialog->getVibrationMotors());
QStringList input_options = g_emu_thread->getInputDeviceListModel()->getVibrationMotorList();
if (!current.isEmpty() && input_options.indexOf(current) < 0)
{
input_options.append(current);

View File

@ -122,7 +122,8 @@ static bool s_cleanup_after_update = false;
EmuThread* g_emu_thread = nullptr;
EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread)
EmuThread::EmuThread(QThread* ui_thread)
: QThread(), m_ui_thread(ui_thread), m_input_device_list_model(std::make_unique<InputDeviceListModel>())
{
}
@ -1079,34 +1080,6 @@ void EmuThread::closeInputSources()
InputManager::CloseSources();
}
void EmuThread::enumerateInputDevices()
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, &EmuThread::enumerateInputDevices, Qt::QueuedConnection);
return;
}
onInputDevicesEnumerated(InputManager::EnumerateDevices());
}
void EmuThread::enumerateVibrationMotors()
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, &EmuThread::enumerateVibrationMotors, Qt::QueuedConnection);
return;
}
const std::vector<InputBindingKey> motors(InputManager::EnumerateMotors());
QList<InputBindingKey> qmotors;
qmotors.reserve(motors.size());
for (InputBindingKey key : motors)
qmotors.push_back(key);
onVibrationMotorsEnumerated(qmotors);
}
void EmuThread::confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept,
std::function<void(bool)> callback) const
{
@ -1830,9 +1803,9 @@ void EmuThread::start()
AssertMsg(!g_emu_thread, "Emu thread does not exist");
g_emu_thread = new EmuThread(QThread::currentThread());
g_emu_thread->moveToThread(g_emu_thread);
g_emu_thread->QThread::start();
g_emu_thread->m_started_semaphore.acquire();
g_emu_thread->moveToThread(g_emu_thread);
}
void EmuThread::stop()
@ -1868,7 +1841,10 @@ void EmuThread::run()
}
}
// bind buttons/axises
// enumerate all devices, even those which were added early
m_input_device_list_model->enumerateDevices();
// start background input polling
createBackgroundControllerPollTimer();
startBackgroundControllerPollTimer();
@ -2025,13 +2001,126 @@ void Host::ReportDebuggerMessage(std::string_view message)
emit g_emu_thread->debuggerMessageReported(QString::fromUtf8(message));
}
void Host::AddFixedInputBindings(const SettingsInterface& si)
InputDeviceListModel::InputDeviceListModel(QObject* parent) : QAbstractListModel(parent)
{
}
void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view device_name)
InputDeviceListModel::~InputDeviceListModel() = default;
int InputDeviceListModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
emit g_emu_thread->onInputDeviceConnected(std::string(identifier), std::string(device_name));
return m_devices.size();
}
QVariant InputDeviceListModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
const int row = index.row();
if (index.column() != 0 || row < 0 || static_cast<qsizetype>(row) >= m_devices.size())
return QVariant();
const auto& dev = m_devices[static_cast<qsizetype>(row)];
if (role == Qt::DisplayRole)
return QStringLiteral("%1: %2").arg(dev.first).arg(dev.second);
else
return QVariant();
}
void InputDeviceListModel::enumerateDevices()
{
DebugAssert(g_emu_thread->isCurrentThread());
const InputManager::DeviceList devices = InputManager::EnumerateDevices();
const InputManager::VibrationMotorList motors = InputManager::EnumerateVibrationMotors();
DeviceList new_devices;
new_devices.reserve(devices.size());
for (const auto& [key, identifier, device_name] : devices)
new_devices.emplace_back(QString::fromStdString(identifier), QString::fromStdString(device_name));
QStringList new_motors;
new_motors.reserve(motors.size());
for (const auto& key : motors)
{
new_motors.push_back(
QString::fromStdString(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, key)));
}
QMetaObject::invokeMethod(this, "resetLists", Qt::QueuedConnection, Q_ARG(const DeviceList&, new_devices),
Q_ARG(const QStringList&, m_vibration_motors));
}
void InputDeviceListModel::resetLists(const DeviceList& devices, const QStringList& motors)
{
beginResetModel();
m_devices = devices;
m_vibration_motors = motors;
endResetModel();
}
void InputDeviceListModel::onDeviceConnected(const QString& identifier, const QString& device_name,
const QStringList& vibration_motors)
{
for (const auto& it : m_devices)
{
if (it.first == identifier)
return;
}
const int index = static_cast<int>(m_devices.size());
beginInsertRows(QModelIndex(), index, index);
m_devices.emplace_back(identifier, device_name);
endInsertRows();
m_vibration_motors.append(vibration_motors);
}
void InputDeviceListModel::onDeviceDisconnected(const QString& identifier)
{
for (qsizetype i = 0; i < m_devices.size(); i++)
{
if (m_devices[i].first == identifier)
{
const int index = static_cast<int>(i);
beginRemoveRows(QModelIndex(), index, index);
m_devices.remove(i);
endRemoveRows();
// remove vibration motors too
const QString motor_prefix = QStringLiteral("%1/").arg(identifier);
for (qsizetype j = 0; j < m_vibration_motors.size();)
{
if (m_vibration_motors[j].startsWith(motor_prefix))
m_vibration_motors.remove(j);
else
j++;
}
return;
}
}
}
void Host::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name)
{
// get the motors for this device to append to the list
QStringList vibration_motor_list;
const InputManager::VibrationMotorList im_vibration_motor_list = InputManager::EnumerateVibrationMotors(key);
if (!im_vibration_motor_list.empty())
{
vibration_motor_list.reserve(im_vibration_motor_list.size());
for (const InputBindingKey& motor_key : im_vibration_motor_list)
{
vibration_motor_list.push_back(
QString::fromStdString(InputManager::ConvertInputBindingKeyToString(InputBindingInfo::Type::Motor, motor_key)));
}
}
QMetaObject::invokeMethod(g_emu_thread->getInputDeviceListModel(), "onDeviceConnected", Qt::QueuedConnection,
Q_ARG(const QString&, QtUtils::StringViewToQString(identifier)),
Q_ARG(const QString&, QtUtils::StringViewToQString(device_name)),
Q_ARG(const QStringList&, vibration_motor_list));
if (System::IsValid() || GPUThread::IsFullscreenUIRequested())
{
@ -2043,7 +2132,8 @@ void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view
void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
{
emit g_emu_thread->onInputDeviceDisconnected(std::string(identifier));
QMetaObject::invokeMethod(g_emu_thread->getInputDeviceListModel(), "onDeviceDisconnected", Qt::QueuedConnection,
Q_ARG(const QString&, QtUtils::StringViewToQString(identifier)));
if (g_settings.pause_on_controller_disconnection && System::GetState() == System::State::Running &&
InputManager::HasAnyBindingsForSource(key))
@ -2067,6 +2157,10 @@ void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view ident
}
}
void Host::AddFixedInputBindings(const SettingsInterface& si)
{
}
std::string QtHost::GetResourcePath(std::string_view filename, bool allow_override)
{
return allow_override ? EmuFolders::GetOverridableResourcePath(filename) :

View File

@ -15,8 +15,10 @@
#include "util/input_manager.h"
#include <QtCore/QByteArray>
#include <QtCore/QList>
#include <QtCore/QMetaType>
#include <QtCore/QObject>
#include <QtCore/QPair>
#include <QtCore/QSemaphore>
#include <QtCore/QString>
#include <QtCore/QThread>
@ -48,6 +50,7 @@ class GPUBackend;
class MainWindow;
class DisplayWidget;
class InputDeviceListModel;
namespace Achievements {
enum class LoginRequestReason;
@ -96,6 +99,8 @@ public:
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE InputDeviceListModel* getInputDeviceListModel() const { return m_input_device_list_model.get(); }
std::optional<WindowInfo> acquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
Error* error);
void connectDisplaySignals(DisplayWidget* widget);
@ -130,10 +135,6 @@ Q_SIGNALS:
void statusMessage(const QString& message);
void debuggerMessageReported(const QString& message);
void settingsResetToDefault(bool system, bool controller);
void onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices);
void onInputDeviceConnected(const std::string& identifier, const std::string& device_name);
void onInputDeviceDisconnected(const std::string& identifier);
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
void systemStarting();
void systemStarted();
void systemDestroyed();
@ -177,8 +178,6 @@ public Q_SLOTS:
void reloadInputBindings();
void reloadInputDevices();
void closeInputSources();
void enumerateInputDevices();
void enumerateVibrationMotors();
void startFullscreenUI();
void stopFullscreenUI();
void bootSystem(std::shared_ptr<SystemBootParameters> params);
@ -241,6 +240,7 @@ private:
QSemaphore m_started_semaphore;
QEventLoop* m_event_loop = nullptr;
QTimer* m_background_controller_polling_timer = nullptr;
std::unique_ptr<InputDeviceListModel> m_input_device_list_model;
bool m_shutdown_flag = false;
bool m_is_rendering_to_main = false;
@ -261,6 +261,38 @@ private:
bool m_last_hardware_renderer = false;
};
class InputDeviceListModel final : public QAbstractListModel
{
Q_OBJECT
public:
using DeviceList = QList<QPair<QString, QString>>;
InputDeviceListModel(QObject* parent = nullptr);
~InputDeviceListModel() override;
// Safe to access on UI thread.
ALWAYS_INLINE const DeviceList& getDeviceList() const { return m_devices; }
ALWAYS_INLINE const QStringList& getVibrationMotorList() const { return m_vibration_motors; }
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
// NOTE: Should only be called on EmuThread.
void enumerateDevices();
public Q_SLOTS:
void onDeviceConnected(const QString& identifier, const QString& device_name, const QStringList& vibration_motors);
void onDeviceDisconnected(const QString& identifier);
private Q_SLOTS:
void resetLists(const DeviceList& devices, const QStringList& motors);
private:
DeviceList m_devices;
QStringList m_vibration_motors;
};
extern EmuThread* g_emu_thread;
namespace QtHost {

View File

@ -426,15 +426,6 @@ void SetupWizardDialog::setupControllerPage(bool initial)
}
}
if (initial)
{
// Trigger enumeration to populate the device list.
connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this, &SetupWizardDialog::onInputDevicesEnumerated);
connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &SetupWizardDialog::onInputDeviceConnected);
connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &SetupWizardDialog::onInputDeviceDisconnected);
g_emu_thread->enumerateInputDevices();
}
if (!initial)
{
for (const PadWidgets& w : pad_widgets)
@ -451,13 +442,11 @@ void SetupWizardDialog::openAutomaticMappingMenu(u32 port, QLabel* update_label)
QMenu menu(this);
bool added = false;
for (const auto& [identifier, device_name] : m_device_list)
for (const auto& [identifier, device_name] : g_emu_thread->getInputDeviceListModel()->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
const QString qidentifier = QString::fromStdString(identifier);
QAction* action =
menu.addAction(QStringLiteral("%1 (%2)").arg(qidentifier).arg(QString::fromStdString(device_name)));
action->setData(qidentifier);
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(identifier).arg(device_name));
action->setData(identifier);
connect(action, &QAction::triggered, this, [this, port, update_label, action]() {
doDeviceAutomaticBinding(port, update_label, action->data().toString());
});
@ -499,25 +488,3 @@ void SetupWizardDialog::doDeviceAutomaticBinding(u32 port, QLabel* update_label,
update_label->setText(device);
}
void SetupWizardDialog::onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices)
{
m_device_list = devices;
}
void SetupWizardDialog::onInputDeviceConnected(const std::string& identifier, const std::string& device_name)
{
m_device_list.emplace_back(identifier, device_name);
}
void SetupWizardDialog::onInputDeviceDisconnected(const std::string& identifier)
{
for (auto iter = m_device_list.begin(); iter != m_device_list.end(); ++iter)
{
if (iter->first == identifier)
{
m_device_list.erase(iter);
break;
}
}
}

View File

@ -45,10 +45,6 @@ private Q_SLOTS:
void refreshDirectoryList();
void resizeDirectoryListColumns();
void onInputDevicesEnumerated(const std::vector<std::pair<std::string, std::string>>& devices);
void onInputDeviceConnected(const std::string& identifier, const std::string& device_name);
void onInputDeviceDisconnected(const std::string& identifier);
protected:
void resizeEvent(QResizeEvent* event);
@ -82,6 +78,4 @@ private:
Ui::SetupWizardDialog m_ui;
std::array<QLabel*, Page_Count> m_page_labels;
std::vector<std::pair<std::string, std::string>> m_device_list;
};

View File

@ -481,7 +481,7 @@ void Host::AddFixedInputBindings(const SettingsInterface& si)
// noop
}
void Host::OnInputDeviceConnected(std::string_view identifier, std::string_view device_name)
void Host::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name)
{
// noop
}

View File

@ -1,4 +1,4 @@
// 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
#define INITGUID
@ -153,7 +153,8 @@ bool DInputSource::ReloadDevices()
{
const u32 index = static_cast<u32>(m_controllers.size());
m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(GetDeviceIdentifier(index), name);
InputManager::OnInputDeviceConnected(MakeGenericControllerDeviceKey(InputSourceType::DInput, index),
GetDeviceIdentifier(index), name);
changed = true;
}
}
@ -166,12 +167,7 @@ void DInputSource::Shutdown()
while (!m_controllers.empty())
{
const u32 index = static_cast<u32>(m_controllers.size() - 1);
InputManager::OnInputDeviceDisconnected(InputBindingKey{{.source_type = InputSourceType::DInput,
.source_index = index,
.source_subtype = InputSubclass::None,
.modifier = InputModifier::None,
.invert = 0,
.data = 0}},
InputManager::OnInputDeviceDisconnected(MakeGenericControllerDeviceKey(InputSourceType::DInput, index),
GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1)));
m_controllers.pop_back();
}
@ -298,9 +294,9 @@ void DInputSource::PollEvents()
}
}
std::vector<std::pair<std::string, std::string>> DInputSource::EnumerateDevices()
InputManager::DeviceList DInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
InputManager::DeviceList ret;
for (size_t i = 0; i < m_controllers.size(); i++)
{
DIDEVICEINSTANCEW dii;
@ -312,13 +308,14 @@ std::vector<std::pair<std::string, std::string>> DInputSource::EnumerateDevices(
if (name.empty())
name = "Unknown";
ret.emplace_back(GetDeviceIdentifier(static_cast<u32>(i)), std::move(name));
ret.emplace_back(MakeGenericControllerDeviceKey(InputSourceType::DInput, static_cast<u32>(i)),
GetDeviceIdentifier(static_cast<u32>(i)), std::move(name));
}
return ret;
}
std::vector<InputBindingKey> DInputSource::EnumerateMotors()
InputManager::VibrationMotorList DInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
return {};
}

View File

@ -1,4 +1,4 @@
// 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
#pragma once
@ -39,8 +39,8 @@ public:
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,

View File

@ -1274,9 +1274,9 @@ void InputManager::UpdatePointerCount()
DebugAssert(ris);
s_pointer_count = 0;
for (const std::pair<std::string, std::string>& it : ris->EnumerateDevices())
for (const auto& [key, identifier, device_name] : ris->EnumerateDevices())
{
if (it.first.starts_with("Pointer-"))
if (key.source_type == InputSourceType::Pointer)
s_pointer_count++;
}
#endif
@ -1614,10 +1614,11 @@ std::vector<std::string> InputManager::GetInputProfileNames()
return ret;
}
void InputManager::OnInputDeviceConnected(std::string_view identifier, std::string_view device_name)
void InputManager::OnInputDeviceConnected(InputBindingKey key, std::string_view identifier,
std::string_view device_name)
{
INFO_LOG("Device '{}' connected: '{}'", identifier, device_name);
Host::OnInputDeviceConnected(identifier, device_name);
Host::OnInputDeviceConnected(key, identifier, device_name);
}
void InputManager::OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier)
@ -2006,18 +2007,23 @@ void InputManager::PollSources()
}
}
std::vector<std::pair<std::string, std::string>> InputManager::EnumerateDevices()
InputManager::DeviceList InputManager::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
DeviceList ret;
ret.emplace_back("Keyboard", "Keyboard");
ret.emplace_back("Mouse", "Mouse");
InputBindingKey keyboard_key = {};
keyboard_key.source_type = InputSourceType::Keyboard;
InputBindingKey mouse_key = {};
mouse_key.source_type = InputSourceType::Pointer;
ret.emplace_back(keyboard_key, "Keyboard", "Keyboard");
ret.emplace_back(mouse_key, "Mouse", "Mouse");
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
std::vector<std::pair<std::string, std::string>> devs(s_input_sources[i]->EnumerateDevices());
DeviceList devs = s_input_sources[i]->EnumerateDevices();
if (ret.empty())
ret = std::move(devs);
else
@ -2028,15 +2034,15 @@ std::vector<std::pair<std::string, std::string>> InputManager::EnumerateDevices(
return ret;
}
std::vector<InputBindingKey> InputManager::EnumerateMotors()
InputManager::VibrationMotorList InputManager::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
std::vector<InputBindingKey> ret;
VibrationMotorList ret;
for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++)
{
if (s_input_sources[i])
{
std::vector<InputBindingKey> devs(s_input_sources[i]->EnumerateMotors());
VibrationMotorList devs = s_input_sources[i]->EnumerateVibrationMotors(for_device);
if (ret.empty())
ret = std::move(devs);
else

View File

@ -7,6 +7,7 @@
#include <mutex>
#include <optional>
#include <string_view>
#include <tuple>
#include <utility>
#include <variant>
@ -261,10 +262,12 @@ bool PrettifyInputBinding(SmallStringBase& binding, BindingIconMappingFunction m
std::vector<const HotkeyInfo*> GetHotkeyList();
/// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name.
std::vector<std::pair<std::string, std::string>> EnumerateDevices();
using DeviceList = std::vector<std::tuple<InputBindingKey, std::string, std::string>>;
DeviceList EnumerateDevices();
/// Enumerates available vibration motors at the time of call.
std::vector<InputBindingKey> EnumerateMotors();
using VibrationMotorList = std::vector<InputBindingKey>;
VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device = std::nullopt);
/// Retrieves bindings that match the generic bindings for the specified device.
GenericInputBindingMapping GetGenericBindingMapping(std::string_view device);
@ -380,7 +383,7 @@ bool MapController(SettingsInterface& si, u32 controller,
std::vector<std::string> GetInputProfileNames();
/// Called when a new input device is connected.
void OnInputDeviceConnected(std::string_view identifier, std::string_view device_name);
void OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name);
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier);
@ -394,7 +397,7 @@ namespace Host {
void AddFixedInputBindings(const SettingsInterface& si);
/// Called when a new input device is connected.
void OnInputDeviceConnected(std::string_view identifier, std::string_view device_name);
void OnInputDeviceConnected(InputBindingKey key, std::string_view identifier, std::string_view device_name);
/// Called when an input device is disconnected.
void OnInputDeviceDisconnected(InputBindingKey key, std::string_view identifier);

View File

@ -20,6 +20,16 @@ void InputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey sm
UpdateMotorState(small_key, small_intensity);
}
InputBindingKey InputSource::MakeGenericControllerDeviceKey(InputSourceType clazz, u32 controller_index)
{
InputBindingKey key = {};
key.source_type = clazz;
key.source_index = controller_index;
key.source_subtype = InputSubclass::None;
key.data = 0;
return key;
}
InputBindingKey InputSource::MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index)
{
InputBindingKey key = {};

View File

@ -38,10 +38,10 @@ public:
virtual TinyString ConvertKeyToIcon(InputBindingKey key, InputManager::BindingIconMappingFunction mapper) = 0;
/// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name.
virtual std::vector<std::pair<std::string, std::string>> EnumerateDevices() = 0;
virtual InputManager::DeviceList EnumerateDevices() = 0;
/// Enumerates available vibration motors at the time of call.
virtual std::vector<InputBindingKey> EnumerateMotors() = 0;
virtual InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) = 0;
/// Retrieves bindings that match the generic bindings for the specified device.
/// Returns false if it's not one of our devices.
@ -57,6 +57,9 @@ public:
/// Creates a force-feedback device from this source.
virtual std::unique_ptr<ForceFeedbackDevice> CreateForceFeedbackDevice(std::string_view device, Error* error) = 0;
/// Creates a key for a generic controller device.
static InputBindingKey MakeGenericControllerDeviceKey(InputSourceType clazz, u32 controller_index);
/// Creates a key for a generic controller axis event.
static InputBindingKey MakeGenericControllerAxisKey(InputSourceType clazz, u32 controller_index, s32 axis_index);

View File

@ -1,4 +1,4 @@
// 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
#include "sdl_input_source.h"
@ -361,19 +361,20 @@ void SDLInputSource::PollEvents()
}
}
std::vector<std::pair<std::string, std::string>> SDLInputSource::EnumerateDevices()
InputManager::DeviceList SDLInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
InputManager::DeviceList ret;
for (const ControllerData& cd : m_controllers)
{
std::string id = fmt::format("SDL-{}", cd.player_id);
const InputBindingKey key = MakeGenericControllerDeviceKey(InputSourceType::SDL, cd.player_id);
const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick);
if (name)
ret.emplace_back(std::move(id), name);
ret.emplace_back(key, std::move(id), name);
else
ret.emplace_back(std::move(id), "Unknown Device");
ret.emplace_back(key, std::move(id), "Unknown Device");
}
return ret;
@ -858,7 +859,8 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
m_controllers.push_back(std::move(cd));
InputManager::OnInputDeviceConnected(fmt::format("SDL-{}", player_id), name);
InputManager::OnInputDeviceConnected(MakeGenericControllerDeviceKey(InputSourceType::SDL, player_id),
fmt::format("SDL-{}", player_id), name);
return true;
}
@ -868,12 +870,7 @@ bool SDLInputSource::CloseDevice(int joystick_index)
if (it == m_controllers.end())
return false;
InputManager::OnInputDeviceDisconnected(InputBindingKey{{.source_type = InputSourceType::SDL,
.source_index = static_cast<u32>(it->player_id),
.source_subtype = InputSubclass::None,
.modifier = InputModifier::None,
.invert = 0,
.data = 0}},
InputManager::OnInputDeviceDisconnected(MakeGenericControllerDeviceKey(InputSourceType::SDL, it->player_id),
fmt::format("SDL-{}", it->player_id));
if (it->haptic)
@ -1023,15 +1020,21 @@ bool SDLInputSource::HandleJoystickHatEvent(const SDL_JoyHatEvent* ev)
return true;
}
std::vector<InputBindingKey> SDLInputSource::EnumerateMotors()
InputManager::VibrationMotorList SDLInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
std::vector<InputBindingKey> ret;
InputManager::VibrationMotorList ret;
if (for_device.has_value() && for_device->source_type != InputSourceType::SDL)
return ret;
InputBindingKey key = {};
key.source_type = InputSourceType::SDL;
for (ControllerData& cd : m_controllers)
{
if (for_device.has_value() && for_device->source_index != static_cast<u32>(cd.player_id))
continue;
key.source_index = cd.player_id;
if (cd.use_game_controller_rumble || cd.haptic_left_right_effect)

View File

@ -1,4 +1,4 @@
// 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
#pragma once
@ -27,8 +27,8 @@ public:
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,

View File

@ -1,4 +1,4 @@
// 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
#include "win32_raw_input_source.h"
@ -73,11 +73,14 @@ void Win32RawInputSource::PollEvents()
// noop, handled by message pump
}
std::vector<std::pair<std::string, std::string>> Win32RawInputSource::EnumerateDevices()
InputManager::DeviceList Win32RawInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
InputManager::DeviceList ret;
for (u32 pointer_index = 0; pointer_index < static_cast<u32>(m_mice.size()); pointer_index++)
ret.emplace_back(InputManager::GetPointerDeviceName(pointer_index), GetMouseDeviceName(pointer_index));
{
ret.emplace_back(MakeGenericControllerDeviceKey(InputSourceType::Pointer, pointer_index),
InputManager::GetPointerDeviceName(pointer_index), GetMouseDeviceName(pointer_index));
}
return ret;
}
@ -118,7 +121,8 @@ std::unique_ptr<ForceFeedbackDevice> Win32RawInputSource::CreateForceFeedbackDev
return {};
}
std::vector<InputBindingKey> Win32RawInputSource::EnumerateMotors()
InputManager::VibrationMotorList
Win32RawInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
return {};
}
@ -272,7 +276,10 @@ bool Win32RawInputSource::OpenDevices()
return false;
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
InputManager::OnInputDeviceConnected(InputManager::GetPointerDeviceName(i), GetMouseDeviceName(i));
{
InputManager::OnInputDeviceConnected(MakeGenericControllerDeviceKey(InputSourceType::Pointer, i),
InputManager::GetPointerDeviceName(i), GetMouseDeviceName(i));
}
}
return true;
@ -287,7 +294,7 @@ void Win32RawInputSource::CloseDevices()
for (u32 i = 0; i < static_cast<u32>(m_mice.size()); i++)
{
InputManager::OnInputDeviceDisconnected(InputManager::MakePointerAxisKey(i, InputPointerAxis::X),
InputManager::OnInputDeviceDisconnected(MakeGenericControllerDeviceKey(InputSourceType::Pointer, i),
InputManager::GetPointerDeviceName(i));
}
m_mice.clear();

View File

@ -1,9 +1,12 @@
// 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
#pragma once
#include "common/windows_headers.h"
#include "input_source.h"
#include "common/windows_headers.h"
#include <array>
#include <functional>
#include <mutex>
@ -23,8 +26,8 @@ public:
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,

View File

@ -1,4 +1,4 @@
// 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
#include "xinput_source.h"
@ -237,16 +237,17 @@ void XInputSource::PollEvents()
}
}
std::vector<std::pair<std::string, std::string>> XInputSource::EnumerateDevices()
InputManager::DeviceList XInputSource::EnumerateDevices()
{
std::vector<std::pair<std::string, std::string>> ret;
InputManager::DeviceList ret;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (!m_controllers[i].connected)
continue;
ret.emplace_back(fmt::format("XInput-{}", i), fmt::format("XInput Controller {}", i));
ret.emplace_back(MakeGenericControllerDeviceKey(InputSourceType::XInput, i), fmt::format("XInput-{}", i),
fmt::format("XInput Controller {}", i));
}
return ret;
@ -376,12 +377,18 @@ std::unique_ptr<ForceFeedbackDevice> XInputSource::CreateForceFeedbackDevice(std
return {};
}
std::vector<InputBindingKey> XInputSource::EnumerateMotors()
InputManager::VibrationMotorList XInputSource::EnumerateVibrationMotors(std::optional<InputBindingKey> for_device)
{
std::vector<InputBindingKey> ret;
InputManager::VibrationMotorList ret;
if (for_device.has_value() && for_device->source_type != InputSourceType::XInput)
return ret;
for (u32 i = 0; i < NUM_CONTROLLERS; i++)
{
if (for_device.has_value() && for_device->source_index != i)
continue;
const ControllerData& cd = m_controllers[i];
if (!cd.connected)
continue;
@ -449,19 +456,15 @@ void XInputSource::HandleControllerConnection(u32 index)
cd.has_small_motor = caps.Vibration.wRightMotorSpeed != 0;
cd.last_state = {};
InputManager::OnInputDeviceConnected(fmt::format("XInput-{}", index), fmt::format("XInput Controller {}", index));
InputManager::OnInputDeviceConnected(MakeGenericControllerDeviceKey(InputSourceType::XInput, index),
fmt::format("XInput-{}", index), fmt::format("XInput Controller {}", index));
}
void XInputSource::HandleControllerDisconnection(u32 index)
{
INFO_LOG("XInput controller {} disconnected.", index);
InputManager::OnInputDeviceDisconnected({{.source_type = InputSourceType::XInput,
.source_index = index,
.source_subtype = InputSubclass::None,
.modifier = InputModifier::None,
.invert = 0,
.data = 0}},
InputManager::OnInputDeviceDisconnected(MakeGenericControllerDeviceKey(InputSourceType::XInput, index),
fmt::format("XInput-{}", index));
m_controllers[index] = {};
}

View File

@ -1,4 +1,4 @@
// 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
#pragma once
@ -41,8 +41,8 @@ public:
void Shutdown() override;
void PollEvents() override;
std::vector<std::pair<std::string, std::string>> EnumerateDevices() override;
std::vector<InputBindingKey> EnumerateMotors() override;
InputManager::DeviceList EnumerateDevices() override;
InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional<InputBindingKey> for_device) override;
bool GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping) override;
void UpdateMotorState(InputBindingKey key, float intensity) override;
void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,