diff options
| author | german77 <juangerman-13@hotmail.com> | 2021-11-27 23:26:51 -0600 | 
|---|---|---|
| committer | Narr the Reg <juangerman-13@hotmail.com> | 2021-12-02 15:17:44 -0600 | 
| commit | 5ba7b11ba49ecba530612248286482f94799672c (patch) | |
| tree | 16439df3a2c0d1f1169e45cefea492572413f6ba | |
| parent | 46e3ed5a483eac404ff1e2103ecc4e93e815d45b (diff) | |
yuzu: Implement basic controller navigation
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/yuzu/applets/qt_controller.h | 4 | ||||
| -rw-r--r-- | src/yuzu/applets/qt_profile_select.cpp | 17 | ||||
| -rw-r--r-- | src/yuzu/applets/qt_profile_select.h | 8 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 20 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 5 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 9 | ||||
| -rw-r--r-- | src/yuzu/util/controller_navigation.cpp | 177 | ||||
| -rw-r--r-- | src/yuzu/util/controller_navigation.h | 51 | 
9 files changed, 285 insertions, 8 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index a44815e71..732e8c276 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -152,6 +152,8 @@ add_executable(yuzu      main.ui      uisettings.cpp      uisettings.h +    util/controller_navigation.cpp +    util/controller_navigation.h      util/limitable_input_dialog.cpp      util/limitable_input_dialog.h      util/overlay_dialog.cpp diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index cc343e5ae..7ab9ced3d 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -7,7 +7,6 @@  #include <array>  #include <memory>  #include <QDialog> -#include "core/core.h"  #include "core/frontend/applets/controller.h"  class GMainWindow; @@ -32,8 +31,9 @@ class System;  }  namespace Core::HID { +class HIDCore;  enum class NpadStyleIndex : u8; -} +} // namespace Core::HID  class QtControllerSelectorDialog final : public QDialog {      Q_OBJECT diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp index a56638e21..7b19f1f8d 100644 --- a/src/yuzu/applets/qt_profile_select.cpp +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include <mutex> +#include <QApplication>  #include <QDialogButtonBox>  #include <QHeaderView>  #include <QLabel> @@ -16,6 +17,7 @@  #include "core/hle/lock.h"  #include "yuzu/applets/qt_profile_select.h"  #include "yuzu/main.h" +#include "yuzu/util/controller_navigation.h"  namespace {  QString FormatUserEntryText(const QString& username, Common::UUID uuid) { @@ -45,7 +47,7 @@ QPixmap GetIcon(Common::UUID uuid) {  }  } // Anonymous namespace -QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) +QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent)      : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {      outer_layout = new QVBoxLayout; @@ -65,6 +67,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)      tree_view = new QTreeView;      item_model = new QStandardItemModel(tree_view);      tree_view->setModel(item_model); +    controller_navigation = new ControllerNavigation(hid_core, this);      tree_view->setAlternatingRowColors(true);      tree_view->setSelectionMode(QHeaderView::SingleSelection); @@ -91,6 +94,14 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)      scroll_area->setLayout(layout);      connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); +    connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, +            [this](Qt::Key key) { +                if (!this->isActiveWindow()) { +                    return; +                } +                QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); +                QCoreApplication::postEvent(tree_view, event); +            });      const auto& profiles = profile_manager->GetAllUsers();      for (const auto& user : profiles) { @@ -113,7 +124,9 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)      resize(550, 400);  } -QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; +QtProfileSelectionDialog::~QtProfileSelectionDialog() { +    controller_navigation->UnloadController(); +};  int QtProfileSelectionDialog::exec() {      // Skip profile selection when there's only one. diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h index 4e9037488..56496ed31 100644 --- a/src/yuzu/applets/qt_profile_select.h +++ b/src/yuzu/applets/qt_profile_select.h @@ -11,6 +11,7 @@  #include "core/frontend/applets/profile_select.h"  #include "core/hle/service/acc/profile_manager.h" +class ControllerNavigation;  class GMainWindow;  class QDialogButtonBox;  class QGraphicsScene; @@ -20,11 +21,15 @@ class QStandardItem;  class QStandardItemModel;  class QVBoxLayout; +namespace Core::HID { +class HIDCore; +} // namespace Core::HID +  class QtProfileSelectionDialog final : public QDialog {      Q_OBJECT  public: -    explicit QtProfileSelectionDialog(QWidget* parent); +    explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);      ~QtProfileSelectionDialog() override;      int exec() override; @@ -51,6 +56,7 @@ private:      QDialogButtonBox* buttons;      std::unique_ptr<Service::Account::ProfileManager> profile_manager; +    ControllerNavigation* controller_navigation = nullptr;  };  class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 2af95dbe5..1a5e41588 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -17,6 +17,7 @@  #include <fmt/format.h>  #include "common/common_types.h"  #include "common/logging/log.h" +#include "core/core.h"  #include "core/file_sys/patch_manager.h"  #include "core/file_sys/registered_cache.h"  #include "yuzu/compatibility_list.h" @@ -25,6 +26,7 @@  #include "yuzu/game_list_worker.h"  #include "yuzu/main.h"  #include "yuzu/uisettings.h" +#include "yuzu/util/controller_navigation.h"  GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)      : QObject(parent), gamelist{gamelist} {} @@ -312,6 +314,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide      this->main_window = parent;      layout = new QVBoxLayout;      tree_view = new QTreeView; +    controller_navigation = new ControllerNavigation(system.HIDCore(), this);      search_field = new GameListSearchField(this);      item_model = new QStandardItemModel(tree_view);      tree_view->setModel(item_model); @@ -341,6 +344,18 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide      connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);      connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);      connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded); +    connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, +            [this](Qt::Key key) { +                // Avoid pressing buttons while playing +                if (system.IsPoweredOn()) { +                    return; +                } +                if (!this->isActiveWindow()) { +                    return; +                } +                QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); +                QCoreApplication::postEvent(tree_view, event); +            });      // We must register all custom types with the Qt Automoc system so that we are able to use      // it with signals/slots. In this case, QList falls under the umbrells of custom types. @@ -353,7 +368,12 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide      setLayout(layout);  } +void GameList::UnloadController() { +    controller_navigation->UnloadController(); +} +  GameList::~GameList() { +    UnloadController();      emit ShouldCancelWorker();  } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 675469e66..a94ea1477 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -24,6 +24,7 @@  #include "uisettings.h"  #include "yuzu/compatibility_list.h" +class ControllerNavigation;  class GameListWorker;  class GameListSearchField;  class GameListDir; @@ -88,6 +89,9 @@ public:      void SaveInterfaceLayout();      void LoadInterfaceLayout(); +    /// Disables events from the emulated controller +    void UnloadController(); +      static const QStringList supported_file_extensions;  signals: @@ -143,6 +147,7 @@ private:      QStandardItemModel* item_model = nullptr;      GameListWorker* current_worker = nullptr;      QFileSystemWatcher* watcher = nullptr; +    ControllerNavigation* controller_navigation = nullptr;      CompatibilityList compatibility_list;      friend class GameListSearchField; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9bd0db10a..f266fd963 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -449,7 +449,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(  }  void GMainWindow::ProfileSelectorSelectProfile() { -    QtProfileSelectionDialog dialog(this); +    QtProfileSelectionDialog dialog(system->HIDCore(), this);      dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |                            Qt::WindowTitleHint | Qt::WindowSystemMenuHint |                            Qt::WindowCloseButtonHint); @@ -1346,7 +1346,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p  }  void GMainWindow::SelectAndSetCurrentUser() { -    QtProfileSelectionDialog dialog(this); +    QtProfileSelectionDialog dialog(system->HIDCore(), this);      dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                            Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);      dialog.setWindowModality(Qt::WindowModal); @@ -1608,7 +1608,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target          if (has_user_save) {              // User save data              const auto select_profile = [this] { -                QtProfileSelectionDialog dialog(this); +                QtProfileSelectionDialog dialog(system->HIDCore(), this);                  dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |                                        Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);                  dialog.setWindowModality(Qt::WindowModal); @@ -3376,7 +3376,10 @@ void GMainWindow::closeEvent(QCloseEvent* event) {      UpdateUISettings();      game_list->SaveInterfaceLayout();      hotkey_registry.SaveHotkeys(); + +    // Unload controllers early      controller_dialog->UnloadController(); +    game_list->UnloadController();      system->HIDCore().UnloadInputDevices();      // Shutdown session if the emu thread is active... diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp new file mode 100644 index 000000000..86fb28b9f --- /dev/null +++ b/src/yuzu/util/controller_navigation.cpp @@ -0,0 +1,177 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/settings_input.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "yuzu/util/controller_navigation.h" + +ControllerNavigation::ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent) { +    player1_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); +    handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); +    Core::HID::ControllerUpdateCallback engine_callback{ +        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); }, +        .is_npad_service = false, +    }; +    player1_callback_key = player1_controller->SetCallback(engine_callback); +    handheld_callback_key = handheld_controller->SetCallback(engine_callback); +    is_controller_set = true; +} + +ControllerNavigation::~ControllerNavigation() { +    UnloadController(); +} + +void ControllerNavigation::UnloadController() { +    if (is_controller_set) { +        player1_controller->DeleteCallback(player1_callback_key); +        handheld_controller->DeleteCallback(handheld_callback_key); +        is_controller_set = false; +    } +} + +void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_button, +                                         Qt::Key key) { +    if (button_values[native_button].value && !button_values[native_button].locked) { +        emit TriggerKeyboardEvent(key); +    } +} + +void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) { +    std::lock_guard lock{mutex}; +    if (type == Core::HID::ControllerTriggerType::Button) { +        ControllerUpdateButton(); +        return; +    } + +    if (type == Core::HID::ControllerTriggerType::Stick) { +        ControllerUpdateStick(); +        return; +    } +} + +void ControllerNavigation::ControllerUpdateButton() { +    const auto controller_type = player1_controller->GetNpadStyleIndex(); +    const auto& player1_buttons = player1_controller->GetButtonsValues(); +    const auto& handheld_buttons = handheld_controller->GetButtonsValues(); + +    for (std::size_t i = 0; i < player1_buttons.size(); ++i) { +        const bool button = player1_buttons[i].value || handheld_buttons[i].value; +        // Trigger only once +        button_values[i].locked = button == button_values[i].value; +        button_values[i].value = button; +    } + +    switch (controller_type) { +    case Core::HID::NpadStyleIndex::ProController: +    case Core::HID::NpadStyleIndex::JoyconDual: +    case Core::HID::NpadStyleIndex::Handheld: +    case Core::HID::NpadStyleIndex::GameCube: +        TriggerButton(Settings::NativeButton::A, Qt::Key_Enter); +        TriggerButton(Settings::NativeButton::B, Qt::Key_Escape); +        TriggerButton(Settings::NativeButton::DDown, Qt::Key_Down); +        TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Left); +        TriggerButton(Settings::NativeButton::DRight, Qt::Key_Right); +        TriggerButton(Settings::NativeButton::DUp, Qt::Key_Up); +        break; +    case Core::HID::NpadStyleIndex::JoyconLeft: +        TriggerButton(Settings::NativeButton::DDown, Qt::Key_Enter); +        TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Escape); +        break; +    case Core::HID::NpadStyleIndex::JoyconRight: +        TriggerButton(Settings::NativeButton::X, Qt::Key_Enter); +        TriggerButton(Settings::NativeButton::A, Qt::Key_Escape); +        break; +    default: +        break; +    } +} + +void ControllerNavigation::ControllerUpdateStick() { +    const auto controller_type = player1_controller->GetNpadStyleIndex(); +    const auto& player1_sticks = player1_controller->GetSticksValues(); +    const auto& handheld_sticks = player1_controller->GetSticksValues(); +    bool update = false; + +    for (std::size_t i = 0; i < player1_sticks.size(); ++i) { +        const Common::Input::StickStatus stick{ +            .left = player1_sticks[i].left || handheld_sticks[i].left, +            .right = player1_sticks[i].right || handheld_sticks[i].right, +            .up = player1_sticks[i].up || handheld_sticks[i].up, +            .down = player1_sticks[i].down || handheld_sticks[i].down, +        }; +        // Trigger only once +        if (stick.down != stick_values[i].down || stick.left != stick_values[i].left || +            stick.right != stick_values[i].right || stick.up != stick_values[i].up) { +            update = true; +        } +        stick_values[i] = stick; +    } + +    if (!update) { +        return; +    } + +    switch (controller_type) { +    case Core::HID::NpadStyleIndex::ProController: +    case Core::HID::NpadStyleIndex::JoyconDual: +    case Core::HID::NpadStyleIndex::Handheld: +    case Core::HID::NpadStyleIndex::GameCube: +        if (stick_values[Settings::NativeAnalog::LStick].down) { +            emit TriggerKeyboardEvent(Qt::Key_Down); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].left) { +            emit TriggerKeyboardEvent(Qt::Key_Left); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].right) { +            emit TriggerKeyboardEvent(Qt::Key_Right); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].up) { +            emit TriggerKeyboardEvent(Qt::Key_Up); +            return; +        } +        break; +    case Core::HID::NpadStyleIndex::JoyconLeft: +        if (stick_values[Settings::NativeAnalog::LStick].left) { +            emit TriggerKeyboardEvent(Qt::Key_Down); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].up) { +            emit TriggerKeyboardEvent(Qt::Key_Left); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].down) { +            emit TriggerKeyboardEvent(Qt::Key_Right); +            return; +        } +        if (stick_values[Settings::NativeAnalog::LStick].right) { +            emit TriggerKeyboardEvent(Qt::Key_Up); +            return; +        } +        break; +    case Core::HID::NpadStyleIndex::JoyconRight: +        if (stick_values[Settings::NativeAnalog::RStick].right) { +            emit TriggerKeyboardEvent(Qt::Key_Down); +            return; +        } +        if (stick_values[Settings::NativeAnalog::RStick].down) { +            emit TriggerKeyboardEvent(Qt::Key_Left); +            return; +        } +        if (stick_values[Settings::NativeAnalog::RStick].up) { +            emit TriggerKeyboardEvent(Qt::Key_Right); +            return; +        } +        if (stick_values[Settings::NativeAnalog::RStick].left) { +            emit TriggerKeyboardEvent(Qt::Key_Up); +            return; +        } +        break; +    default: +        break; +    } +} diff --git a/src/yuzu/util/controller_navigation.h b/src/yuzu/util/controller_navigation.h new file mode 100644 index 000000000..7c616a088 --- /dev/null +++ b/src/yuzu/util/controller_navigation.h @@ -0,0 +1,51 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include <QKeyEvent> +#include <QObject> + +#include "common/input.h" +#include "common/settings_input.h" + +namespace Core::HID { +using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; +using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; +enum class ControllerTriggerType; +class EmulatedController; +class HIDCore; +} // namespace Core::HID + +class ControllerNavigation : public QObject { +    Q_OBJECT + +public: +    explicit ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent = nullptr); +    ~ControllerNavigation(); + +    /// Disables events from the emulated controller +    void UnloadController(); + +signals: +    void TriggerKeyboardEvent(Qt::Key key); + +private: +    void TriggerButton(Settings::NativeButton::Values native_button, Qt::Key key); +    void ControllerUpdateEvent(Core::HID::ControllerTriggerType type); + +    void ControllerUpdateButton(); + +    void ControllerUpdateStick(); + +    Core::HID::ButtonValues button_values{}; +    Core::HID::SticksValues stick_values{}; + +    int player1_callback_key{}; +    int handheld_callback_key{}; +    bool is_controller_set{}; +    mutable std::mutex mutex; +    Core::HID::EmulatedController* player1_controller; +    Core::HID::EmulatedController* handheld_controller; +};  | 
