diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 44619a26c..76cd81bda 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -2850,7 +2850,7 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title, void FullscreenUI::StartAutomaticBinding(u32 port) { - std::vector> 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); diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp index 66dfc5684..03e32af86 100644 --- a/src/duckstation-qt/controllerbindingwidgets.cpp +++ b/src/duckstation-qt/controllerbindingwidgets.cpp @@ -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; diff --git a/src/duckstation-qt/controllerglobalsettingswidget.cpp b/src/duckstation-qt/controllerglobalsettingswidget.cpp index db1959e7a..971f144f4 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.cpp +++ b/src/duckstation-qt/controllerglobalsettingswidget.cpp @@ -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); diff --git a/src/duckstation-qt/controllerglobalsettingswidget.h b/src/duckstation-qt/controllerglobalsettingswidget.h index e007a882b..cb38064a8 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.h +++ b/src/duckstation-qt/controllerglobalsettingswidget.h @@ -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(); diff --git a/src/duckstation-qt/controllerglobalsettingswidget.ui b/src/duckstation-qt/controllerglobalsettingswidget.ui index 57e62d1d9..e9e790310 100644 --- a/src/duckstation-qt/controllerglobalsettingswidget.ui +++ b/src/duckstation-qt/controllerglobalsettingswidget.ui @@ -56,7 +56,7 @@ - + 200 diff --git a/src/duckstation-qt/controllersettingswindow.cpp b/src/duckstation-qt/controllersettingswindow.cpp index 7fc7c8a82..a7008af3e 100644 --- a/src/duckstation-qt/controllersettingswindow.cpp +++ b/src/duckstation-qt/controllersettingswindow.cpp @@ -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>& 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& 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 diff --git a/src/duckstation-qt/controllersettingswindow.h b/src/duckstation-qt/controllersettingswindow.h index 5e79aa568..44574f37a 100644 --- a/src/duckstation-qt/controllersettingswindow.h +++ b/src/duckstation-qt/controllersettingswindow.h @@ -48,9 +48,6 @@ public: ALWAYS_INLINE HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; } - ALWAYS_INLINE const std::vector>& 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>& devices); - void onInputDeviceConnected(const std::string& identifier, const std::string& device_name); - void onInputDeviceDisconnected(const std::string& identifier); - void onVibrationMotorsEnumerated(const QList& motors); - void createWidgets(); protected: @@ -119,9 +111,6 @@ private: std::array m_port_bindings{}; HotkeySettingsWidget* m_hotkey_settings = nullptr; - std::vector> m_device_list; - QStringList m_vibration_motors; - QString m_profile_name; std::unique_ptr m_profile_settings_interface; }; diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp index 62a6f492a..bb195e75f 100644 --- a/src/duckstation-qt/inputbindingwidgets.cpp +++ b/src/duckstation-qt/inputbindingwidgets.cpp @@ -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); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index e20797915..6f7030cd0 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -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()) { } @@ -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 motors(InputManager::EnumerateMotors()); - QList 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 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(row) >= m_devices.size()) + return QVariant(); + + const auto& dev = m_devices[static_cast(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(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(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) : diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 095ba881d..36516c690 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -15,8 +15,10 @@ #include "util/input_manager.h" #include +#include #include #include +#include #include #include #include @@ -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 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>& devices); - void onInputDeviceConnected(const std::string& identifier, const std::string& device_name); - void onInputDeviceDisconnected(const std::string& identifier); - void onVibrationMotorsEnumerated(const QList& 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 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 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>; + + 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 { diff --git a/src/duckstation-qt/setupwizarddialog.cpp b/src/duckstation-qt/setupwizarddialog.cpp index 62492c0a4..9bf299b10 100644 --- a/src/duckstation-qt/setupwizarddialog.cpp +++ b/src/duckstation-qt/setupwizarddialog.cpp @@ -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>& 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; - } - } -} diff --git a/src/duckstation-qt/setupwizarddialog.h b/src/duckstation-qt/setupwizarddialog.h index 677012d18..4bb775d62 100644 --- a/src/duckstation-qt/setupwizarddialog.h +++ b/src/duckstation-qt/setupwizarddialog.h @@ -45,10 +45,6 @@ private Q_SLOTS: void refreshDirectoryList(); void resizeDirectoryListColumns(); - void onInputDevicesEnumerated(const std::vector>& 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 m_page_labels; - - std::vector> m_device_list; }; diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index a1ad2d13f..fabd46cdb 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -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 } diff --git a/src/util/dinput_source.cpp b/src/util/dinput_source.cpp index 0682bac63..bbd5b483b 100644 --- a/src/util/dinput_source.cpp +++ b/src/util/dinput_source.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #define INITGUID @@ -153,7 +153,8 @@ bool DInputSource::ReloadDevices() { const u32 index = static_cast(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(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(m_controllers.size() - 1))); m_controllers.pop_back(); } @@ -298,9 +294,9 @@ void DInputSource::PollEvents() } } -std::vector> DInputSource::EnumerateDevices() +InputManager::DeviceList DInputSource::EnumerateDevices() { - std::vector> ret; + InputManager::DeviceList ret; for (size_t i = 0; i < m_controllers.size(); i++) { DIDEVICEINSTANCEW dii; @@ -312,13 +308,14 @@ std::vector> DInputSource::EnumerateDevices( if (name.empty()) name = "Unknown"; - ret.emplace_back(GetDeviceIdentifier(static_cast(i)), std::move(name)); + ret.emplace_back(MakeGenericControllerDeviceKey(InputSourceType::DInput, static_cast(i)), + GetDeviceIdentifier(static_cast(i)), std::move(name)); } return ret; } -std::vector DInputSource::EnumerateMotors() +InputManager::VibrationMotorList DInputSource::EnumerateVibrationMotors(std::optional for_device) { return {}; } diff --git a/src/util/dinput_source.h b/src/util/dinput_source.h index a748fd1a6..cc9bd4877 100644 --- a/src/util/dinput_source.h +++ b/src/util/dinput_source.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -39,8 +39,8 @@ public: void Shutdown() override; void PollEvents() override; - std::vector> EnumerateDevices() override; - std::vector EnumerateMotors() override; + InputManager::DeviceList EnumerateDevices() override; + InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional 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, diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index d62432be5..191879f42 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -1274,9 +1274,9 @@ void InputManager::UpdatePointerCount() DebugAssert(ris); s_pointer_count = 0; - for (const std::pair& 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 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> InputManager::EnumerateDevices() +InputManager::DeviceList InputManager::EnumerateDevices() { - std::vector> 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> 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> InputManager::EnumerateDevices( return ret; } -std::vector InputManager::EnumerateMotors() +InputManager::VibrationMotorList InputManager::EnumerateVibrationMotors(std::optional for_device) { - std::vector ret; + VibrationMotorList ret; for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) { if (s_input_sources[i]) { - std::vector devs(s_input_sources[i]->EnumerateMotors()); + VibrationMotorList devs = s_input_sources[i]->EnumerateVibrationMotors(for_device); if (ret.empty()) ret = std::move(devs); else diff --git a/src/util/input_manager.h b/src/util/input_manager.h index 7d595c2b0..9a2bfddc0 100644 --- a/src/util/input_manager.h +++ b/src/util/input_manager.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -254,17 +255,19 @@ std::string ConvertInputBindingKeysToString(InputBindingInfo::Type binding_type, /// Represents a binding with icon fonts, if available. /// Optionally maps icon fonts to a different style, e.g. xbox icons -> PS buttons. -using BindingIconMappingFunction = std::string_view(*)(std::string_view); +using BindingIconMappingFunction = std::string_view (*)(std::string_view); bool PrettifyInputBinding(SmallStringBase& binding, BindingIconMappingFunction mapper = nullptr); /// Returns a list of all hotkeys. std::vector GetHotkeyList(); /// Enumerates available devices. Returns a pair of the prefix (e.g. SDL-0) and the device name. -std::vector> EnumerateDevices(); +using DeviceList = std::vector>; +DeviceList EnumerateDevices(); /// Enumerates available vibration motors at the time of call. -std::vector EnumerateMotors(); +using VibrationMotorList = std::vector; +VibrationMotorList EnumerateVibrationMotors(std::optional 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 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); diff --git a/src/util/input_source.cpp b/src/util/input_source.cpp index 165bd4af7..8544f16b4 100644 --- a/src/util/input_source.cpp +++ b/src/util/input_source.cpp @@ -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 = {}; diff --git a/src/util/input_source.h b/src/util/input_source.h index 95bf93a7b..4cf485968 100644 --- a/src/util/input_source.h +++ b/src/util/input_source.h @@ -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> EnumerateDevices() = 0; + virtual InputManager::DeviceList EnumerateDevices() = 0; /// Enumerates available vibration motors at the time of call. - virtual std::vector EnumerateMotors() = 0; + virtual InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional 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 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); diff --git a/src/util/sdl_input_source.cpp b/src/util/sdl_input_source.cpp index ad23f8651..2b00f23d4 100644 --- a/src/util/sdl_input_source.cpp +++ b/src/util/sdl_input_source.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "sdl_input_source.h" @@ -361,19 +361,20 @@ void SDLInputSource::PollEvents() } } -std::vector> SDLInputSource::EnumerateDevices() +InputManager::DeviceList SDLInputSource::EnumerateDevices() { - std::vector> 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(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 SDLInputSource::EnumerateMotors() +InputManager::VibrationMotorList SDLInputSource::EnumerateVibrationMotors(std::optional for_device) { - std::vector 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(cd.player_id)) + continue; + key.source_index = cd.player_id; if (cd.use_game_controller_rumble || cd.haptic_left_right_effect) diff --git a/src/util/sdl_input_source.h b/src/util/sdl_input_source.h index 0485d6370..4ce9975ee 100644 --- a/src/util/sdl_input_source.h +++ b/src/util/sdl_input_source.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -27,8 +27,8 @@ public: void Shutdown() override; void PollEvents() override; - std::vector> EnumerateDevices() override; - std::vector EnumerateMotors() override; + InputManager::DeviceList EnumerateDevices() override; + InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional 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, diff --git a/src/util/win32_raw_input_source.cpp b/src/util/win32_raw_input_source.cpp index 9053cd6ea..a49bf0bbc 100644 --- a/src/util/win32_raw_input_source.cpp +++ b/src/util/win32_raw_input_source.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // 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> Win32RawInputSource::EnumerateDevices() +InputManager::DeviceList Win32RawInputSource::EnumerateDevices() { - std::vector> ret; + InputManager::DeviceList ret; for (u32 pointer_index = 0; pointer_index < static_cast(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 Win32RawInputSource::CreateForceFeedbackDev return {}; } -std::vector Win32RawInputSource::EnumerateMotors() +InputManager::VibrationMotorList +Win32RawInputSource::EnumerateVibrationMotors(std::optional for_device) { return {}; } @@ -272,7 +276,10 @@ bool Win32RawInputSource::OpenDevices() return false; for (u32 i = 0; i < static_cast(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(m_mice.size()); i++) { - InputManager::OnInputDeviceDisconnected(InputManager::MakePointerAxisKey(i, InputPointerAxis::X), + InputManager::OnInputDeviceDisconnected(MakeGenericControllerDeviceKey(InputSourceType::Pointer, i), InputManager::GetPointerDeviceName(i)); } m_mice.clear(); diff --git a/src/util/win32_raw_input_source.h b/src/util/win32_raw_input_source.h index f318401a9..4eb9d2ebd 100644 --- a/src/util/win32_raw_input_source.h +++ b/src/util/win32_raw_input_source.h @@ -1,9 +1,12 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // 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 #include #include @@ -23,8 +26,8 @@ public: void Shutdown() override; void PollEvents() override; - std::vector> EnumerateDevices() override; - std::vector EnumerateMotors() override; + InputManager::DeviceList EnumerateDevices() override; + InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional 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, diff --git a/src/util/xinput_source.cpp b/src/util/xinput_source.cpp index 8725505f2..c03d03803 100644 --- a/src/util/xinput_source.cpp +++ b/src/util/xinput_source.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "xinput_source.h" @@ -237,16 +237,17 @@ void XInputSource::PollEvents() } } -std::vector> XInputSource::EnumerateDevices() +InputManager::DeviceList XInputSource::EnumerateDevices() { - std::vector> 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 XInputSource::CreateForceFeedbackDevice(std return {}; } -std::vector XInputSource::EnumerateMotors() +InputManager::VibrationMotorList XInputSource::EnumerateVibrationMotors(std::optional for_device) { - std::vector 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] = {}; } diff --git a/src/util/xinput_source.h b/src/util/xinput_source.h index 1f4ab791d..cd45c85a1 100644 --- a/src/util/xinput_source.h +++ b/src/util/xinput_source.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once @@ -41,8 +41,8 @@ public: void Shutdown() override; void PollEvents() override; - std::vector> EnumerateDevices() override; - std::vector EnumerateMotors() override; + InputManager::DeviceList EnumerateDevices() override; + InputManager::VibrationMotorList EnumerateVibrationMotors(std::optional 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,