From 72c8a94a6cdb4d3f322fa6d4b06eab824f53dba6 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 15 Nov 2021 17:57:41 -0600
Subject: [PATCH] yuzu: Add controller hotkeys

---
 src/core/hid/emulated_controller.cpp         |  19 ++
 src/core/hid/emulated_controller.h           |  10 +
 src/input_common/drivers/sdl_driver.cpp      |   2 +
 src/input_common/drivers/sdl_driver.h        |   2 +-
 src/input_common/drivers/tas_input.cpp       |   7 +-
 src/yuzu/configuration/config.cpp            |  54 +++--
 src/yuzu/configuration/configure_dialog.cpp  |   2 +-
 src/yuzu/configuration/configure_hotkeys.cpp | 242 ++++++++++++++++---
 src/yuzu/configuration/configure_hotkeys.h   |  23 +-
 src/yuzu/hotkeys.cpp                         | 165 ++++++++++++-
 src/yuzu/hotkeys.h                           |  46 ++++
 src/yuzu/main.cpp                            |  64 ++++-
 src/yuzu/main.h                              |  17 +-
 src/yuzu/uisettings.h                        |   6 +-
 14 files changed, 580 insertions(+), 79 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 9f68a41cc..6209c707e 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -351,6 +351,19 @@ void EmulatedController::DisableConfiguration() {
     }
 }
 
+void EmulatedController::EnableSystemButtons() {
+    system_buttons_enabled = true;
+}
+
+void EmulatedController::DisableSystemButtons() {
+    system_buttons_enabled = false;
+}
+
+void EmulatedController::ResetSystemButtons() {
+    controller.home_button_state.home.Assign(false);
+    controller.capture_button_state.capture.Assign(false);
+}
+
 bool EmulatedController::IsConfiguring() const {
     return is_configuring;
 }
@@ -596,9 +609,15 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
             controller.npad_button_state.right_sr.Assign(current_status.value);
             break;
         case Settings::NativeButton::Home:
+            if (!system_buttons_enabled) {
+                break;
+            }
             controller.home_button_state.home.Assign(current_status.value);
             break;
         case Settings::NativeButton::Screenshot:
+            if (!system_buttons_enabled) {
+                break;
+            }
             controller.capture_button_state.capture.Assign(current_status.value);
             break;
         }
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index bee16a8ed..a63a83cce 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -200,6 +200,15 @@ public:
     /// Returns the emulated controller into normal mode, allowing the modification of the HID state
     void DisableConfiguration();
 
+    /// Enables Home and Screenshot buttons
+    void EnableSystemButtons();
+
+    /// Disables Home and Screenshot buttons
+    void DisableSystemButtons();
+
+    /// Sets Home and Screenshot buttons to false
+    void ResetSystemButtons();
+
     /// Returns true if the emulated controller is in configuring mode
     bool IsConfiguring() const;
 
@@ -391,6 +400,7 @@ private:
     NpadStyleTag supported_style_tag{NpadStyleSet::All};
     bool is_connected{false};
     bool is_configuring{false};
+    bool system_buttons_enabled{true};
     f32 motion_sensitivity{0.01f};
     bool force_update_motion{false};
 
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 0cda9df62..757117f2b 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -663,6 +663,7 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
         {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
         {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
         {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+        {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
     };
 }
 
@@ -699,6 +700,7 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding(
         {Settings::NativeButton::SL, sl_button},
         {Settings::NativeButton::SR, sr_button},
         {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
+        {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
     };
 }
 
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index e9a5d2e26..4cde3606f 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -24,7 +24,7 @@ namespace InputCommon {
 class SDLJoystick;
 
 using ButtonBindings =
-    std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
+    std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>;
 using ZButtonBindings =
     std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
 
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
index 5bdd5dac3..579fd9473 100644
--- a/src/input_common/drivers/tas_input.cpp
+++ b/src/input_common/drivers/tas_input.cpp
@@ -23,7 +23,7 @@ enum class Tas::TasAxis : u8 {
 };
 
 // Supported keywords and buttons from a TAS file
-constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
+constexpr std::array<std::pair<std::string_view, TasButton>, 18> text_to_tas_button = {
     std::pair{"KEY_A", TasButton::BUTTON_A},
     {"KEY_B", TasButton::BUTTON_B},
     {"KEY_X", TasButton::BUTTON_X},
@@ -40,8 +40,9 @@ constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_but
     {"KEY_DDOWN", TasButton::BUTTON_DOWN},
     {"KEY_SL", TasButton::BUTTON_SL},
     {"KEY_SR", TasButton::BUTTON_SR},
-    {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
-    {"KEY_HOME", TasButton::BUTTON_HOME},
+    // These buttons are disabled to avoid TAS input from activating hotkeys
+    // {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
+    // {"KEY_HOME", TasButton::BUTTON_HOME},
     {"KEY_ZL", TasButton::TRIGGER_ZL},
     {"KEY_ZR", TasButton::TRIGGER_ZR},
 };
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0f679c37e..99a7397fc 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -66,27 +66,27 @@ const std::array<int, 2> Config::default_stick_mod = {
 // UISetting::values.shortcuts, which is alphabetically ordered.
 // clang-format off
 const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{
-    {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
-    {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
-    {QStringLiteral("Decrease Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Exit Fullscreen"),          QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
-    {QStringLiteral("Exit yuzu"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
-    {QStringLiteral("Fullscreen"),               QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
-    {QStringLiteral("Increase Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
-    {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
-    {QStringLiteral("Mute Audio"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
-    {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
-    {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
-    {QStringLiteral("TAS Start/Stop"),           QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}},
-    {QStringLiteral("TAS Reset"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}},
-    {QStringLiteral("TAS Record"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
-    {QStringLiteral("Toggle Framerate Limit"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Toggle Mouse Panning"),     QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
-    {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
+    {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"),  QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
+    {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"),     QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
+    {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"),      QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
+    {QStringLiteral("Decrease Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("-"),       QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("Exit Fullscreen"),          QStringLiteral("Main Window"), {QStringLiteral("Esc"),     QStringLiteral(""), Qt::WindowShortcut}},
+    {QStringLiteral("Exit yuzu"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"),  QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
+    {QStringLiteral("Fullscreen"),               QStringLiteral("Main Window"), {QStringLiteral("F11"),     QStringLiteral("Home+B"), Qt::WindowShortcut}},
+    {QStringLiteral("Increase Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("+"),       QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"),      QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
+    {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"),  QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
+    {QStringLiteral("Mute Audio"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"),  QStringLiteral(""), Qt::WindowShortcut}},
+    {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"),      QStringLiteral(""), Qt::WindowShortcut}},
+    {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"),      QStringLiteral(""), Qt::WindowShortcut}},
+    {QStringLiteral("TAS Start/Stop"),           QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("TAS Reset"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("TAS Record"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"),  QStringLiteral(""), Qt::WindowShortcut}},
+    {QStringLiteral("Toggle Framerate Limit"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"),  QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
+    {QStringLiteral("Toggle Mouse Panning"),     QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"),  QStringLiteral(""), Qt::ApplicationShortcut}},
+    {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"),  QStringLiteral(""), Qt::WindowShortcut}},
 }};
 // clang-format on
 
@@ -679,7 +679,6 @@ void Config::ReadShortcutValues() {
     qt_config->beginGroup(QStringLiteral("Shortcuts"));
 
     for (const auto& [name, group, shortcut] : default_hotkeys) {
-        const auto& [keyseq, context] = shortcut;
         qt_config->beginGroup(group);
         qt_config->beginGroup(name);
         // No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
@@ -688,7 +687,10 @@ void Config::ReadShortcutValues() {
         UISettings::values.shortcuts.push_back(
             {name,
              group,
-             {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
+             {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(),
+              ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq)
+                  .toString(),
+              shortcut.context}});
         qt_config->endGroup();
         qt_config->endGroup();
     }
@@ -1227,8 +1229,10 @@ void Config::SaveShortcutValues() {
 
         qt_config->beginGroup(group);
         qt_config->beginGroup(name);
-        WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first);
-        WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second);
+        WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq);
+        WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq,
+                     default_hotkey.controller_keyseq);
+        WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context);
         qt_config->endGroup();
         qt_config->endGroup();
     }
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 642a5f966..464e7a489 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -45,7 +45,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
       general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
       graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)},
       graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
-      hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
+      hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
       input_tab{std::make_unique<ConfigureInput>(system_, this)},
       network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
       profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)},
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index ed76fe18e..be10e0a31 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -5,15 +5,24 @@
 #include <QMenu>
 #include <QMessageBox>
 #include <QStandardItemModel>
-#include "common/settings.h"
+#include <QTimer>
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+
 #include "ui_configure_hotkeys.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_hotkeys.h"
 #include "yuzu/hotkeys.h"
 #include "yuzu/util/sequence_dialog/sequence_dialog.h"
 
-ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
-    : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
+constexpr int name_column = 0;
+constexpr int hotkey_column = 1;
+constexpr int controller_column = 2;
+
+ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent)
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()),
+      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
     ui->setupUi(this);
     setFocusPolicy(Qt::ClickFocus);
 
@@ -26,16 +35,24 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
     ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
     ui->hotkey_list->setModel(model);
 
-    // TODO(Kloen): Make context configurable as well (hiding the column for now)
-    ui->hotkey_list->hideColumn(2);
-
-    ui->hotkey_list->setColumnWidth(0, 200);
-    ui->hotkey_list->resizeColumnToContents(1);
+    ui->hotkey_list->setColumnWidth(name_column, 200);
+    ui->hotkey_list->resizeColumnToContents(hotkey_column);
 
     connect(ui->button_restore_defaults, &QPushButton::clicked, this,
             &ConfigureHotkeys::RestoreDefaults);
     connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll);
 
+    controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+
+    connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
+
+    connect(poll_timer.get(), &QTimer::timeout, [this] {
+        const auto buttons = controller->GetNpadButtons();
+        if (buttons.raw != Core::HID::NpadButton::None) {
+            SetPollingResult(buttons.raw, false);
+            return;
+        }
+    });
     RetranslateUI();
 }
 
@@ -49,15 +66,18 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
             auto* action = new QStandardItem(hotkey.first);
             auto* keyseq =
                 new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
+            auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
             action->setEditable(false);
             keyseq->setEditable(false);
-            parent_item->appendRow({action, keyseq});
+            controller_keyseq->setEditable(false);
+            parent_item->appendRow({action, keyseq, controller_keyseq});
         }
         model->appendRow(parent_item);
     }
 
     ui->hotkey_list->expandAll();
-    ui->hotkey_list->resizeColumnToContents(0);
+    ui->hotkey_list->resizeColumnToContents(name_column);
+    ui->hotkey_list->resizeColumnToContents(hotkey_column);
 }
 
 void ConfigureHotkeys::changeEvent(QEvent* event) {
@@ -71,7 +91,7 @@ void ConfigureHotkeys::changeEvent(QEvent* event) {
 void ConfigureHotkeys::RetranslateUI() {
     ui->retranslateUi(this);
 
-    model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
+    model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")});
 }
 
 void ConfigureHotkeys::Configure(QModelIndex index) {
@@ -79,7 +99,15 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
         return;
     }
 
-    index = index.sibling(index.row(), 1);
+    // Controller configuration is selected
+    if (index.column() == controller_column) {
+        ConfigureController(index);
+        return;
+    }
+
+    // Swap to the hotkey column
+    index = index.sibling(index.row(), hotkey_column);
+
     const auto previous_key = model->data(index);
 
     SequenceDialog hotkey_dialog{this};
@@ -99,13 +127,113 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
         model->setData(index, key_sequence.toString(QKeySequence::NativeText));
     }
 }
+void ConfigureHotkeys::ConfigureController(QModelIndex index) {
+    if (timeout_timer->isActive()) {
+        return;
+    }
+
+    const auto previous_key = model->data(index);
+
+    input_setter = [this, index, previous_key](const Core::HID::NpadButton button,
+                                               const bool cancel) {
+        if (cancel) {
+            model->setData(index, previous_key);
+            return;
+        }
+
+        const QString button_string = tr("Home+%1").arg(GetButtonName(button));
+
+        const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string);
+
+        if (key_sequence_used) {
+            QMessageBox::warning(
+                this, tr("Conflicting Key Sequence"),
+                tr("The entered key sequence is already assigned to: %1").arg(used_action));
+            model->setData(index, previous_key);
+        } else {
+            model->setData(index, button_string);
+        }
+    };
+
+    model->setData(index, tr("[waiting]"));
+    timeout_timer->start(2500); // Cancel after 2.5 seconds
+    poll_timer->start(200);     // Check for new inputs every 200ms
+    // We need to disable configuration to be able to read npad buttons
+    controller->DisableConfiguration();
+    controller->DisableSystemButtons();
+}
+
+void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) {
+    timeout_timer->stop();
+    poll_timer->stop();
+    // Re-Enable configuration
+    controller->EnableConfiguration();
+    controller->EnableSystemButtons();
+
+    (*input_setter)(button, cancel);
+
+    input_setter = std::nullopt;
+}
+
+QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const {
+    Core::HID::NpadButtonState state{button};
+    if (state.a) {
+        return tr("A");
+    }
+    if (state.b) {
+        return tr("B");
+    }
+    if (state.x) {
+        return tr("X");
+    }
+    if (state.y) {
+        return tr("Y");
+    }
+    if (state.l || state.right_sl || state.left_sl) {
+        return tr("L");
+    }
+    if (state.r || state.right_sr || state.left_sr) {
+        return tr("R");
+    }
+    if (state.zl) {
+        return tr("ZL");
+    }
+    if (state.zr) {
+        return tr("ZR");
+    }
+    if (state.left) {
+        return tr("Dpad_Left");
+    }
+    if (state.right) {
+        return tr("Dpad_Right");
+    }
+    if (state.up) {
+        return tr("Dpad_Up");
+    }
+    if (state.down) {
+        return tr("Dpad_Down");
+    }
+    if (state.stick_l) {
+        return tr("Left_Stick");
+    }
+    if (state.stick_r) {
+        return tr("Right_Stick");
+    }
+    if (state.minus) {
+        return tr("Minus");
+    }
+    if (state.plus) {
+        return tr("Plus");
+    }
+    return tr("Invalid");
+}
 
 std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
     for (int r = 0; r < model->rowCount(); ++r) {
         const QStandardItem* const parent = model->item(r, 0);
 
         for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
-            const QStandardItem* const key_seq_item = parent->child(r2, 1);
+            const QStandardItem* const key_seq_item = parent->child(r2, hotkey_column);
             const auto key_seq_str = key_seq_item->text();
             const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
 
@@ -118,12 +246,31 @@ std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence)
     return std::make_pair(false, QString());
 }
 
+std::pair<bool, QString> ConfigureHotkeys::IsUsedControllerKey(const QString& key_sequence) const {
+    for (int r = 0; r < model->rowCount(); ++r) {
+        const QStandardItem* const parent = model->item(r, 0);
+
+        for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
+            const QStandardItem* const key_seq_item = parent->child(r2, controller_column);
+            const auto key_seq_str = key_seq_item->text();
+
+            if (key_sequence == key_seq_str) {
+                return std::make_pair(true, parent->child(r2, 0)->text());
+            }
+        }
+    }
+
+    return std::make_pair(false, QString());
+}
+
 void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
     for (int key_id = 0; key_id < model->rowCount(); key_id++) {
         const QStandardItem* parent = model->item(key_id, 0);
         for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
-            const QStandardItem* action = parent->child(key_column_id, 0);
-            const QStandardItem* keyseq = parent->child(key_column_id, 1);
+            const QStandardItem* action = parent->child(key_column_id, name_column);
+            const QStandardItem* keyseq = parent->child(key_column_id, hotkey_column);
+            const QStandardItem* controller_keyseq =
+                parent->child(key_column_id, controller_column);
             for (auto& [group, sub_actions] : registry.hotkey_groups) {
                 if (group != parent->text())
                     continue;
@@ -131,6 +278,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
                     if (action_name != action->text())
                         continue;
                     hotkey.keyseq = QKeySequence(keyseq->text());
+                    hotkey.controller_keyseq = controller_keyseq->text();
                 }
             }
         }
@@ -144,7 +292,12 @@ void ConfigureHotkeys::RestoreDefaults() {
         const QStandardItem* parent = model->item(r, 0);
 
         for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
-            model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first);
+            model->item(r, 0)
+                ->child(r2, hotkey_column)
+                ->setText(Config::default_hotkeys[r2].shortcut.keyseq);
+            model->item(r, 0)
+                ->child(r2, controller_column)
+                ->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq);
         }
     }
 }
@@ -154,7 +307,8 @@ void ConfigureHotkeys::ClearAll() {
         const QStandardItem* parent = model->item(r, 0);
 
         for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
-            model->item(r, 0)->child(r2, 1)->setText(QString{});
+            model->item(r, 0)->child(r2, hotkey_column)->setText(QString{});
+            model->item(r, 0)->child(r2, controller_column)->setText(QString{});
         }
     }
 }
@@ -165,28 +319,52 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
         return;
     }
 
-    const auto selected = index.sibling(index.row(), 1);
+    // Swap to the hotkey column if the controller hotkey column is not selected
+    if (index.column() != controller_column) {
+        index = index.sibling(index.row(), hotkey_column);
+    }
+
     QMenu context_menu;
 
     QAction* restore_default = context_menu.addAction(tr("Restore Default"));
     QAction* clear = context_menu.addAction(tr("Clear"));
 
-    connect(restore_default, &QAction::triggered, [this, selected] {
-        const QKeySequence& default_key_sequence = QKeySequence::fromString(
-            Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText);
-        const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
-
-        if (key_sequence_used &&
-            default_key_sequence != QKeySequence(model->data(selected).toString())) {
-
-            QMessageBox::warning(
-                this, tr("Conflicting Key Sequence"),
-                tr("The default key sequence is already assigned to: %1").arg(used_action));
-        } else {
-            model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
+    connect(restore_default, &QAction::triggered, [this, index] {
+        if (index.column() == controller_column) {
+            RestoreControllerHotkey(index);
+            return;
         }
+        RestoreHotkey(index);
     });
-    connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
+    connect(clear, &QAction::triggered, [this, index] { model->setData(index, QString{}); });
 
     context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
 }
+
+void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
+    const QString& default_key_sequence =
+        Config::default_hotkeys[index.row()].shortcut.controller_keyseq;
+    const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence);
+
+    if (key_sequence_used && default_key_sequence != model->data(index).toString()) {
+        QMessageBox::warning(
+            this, tr("Conflicting Button Sequence"),
+            tr("The default button sequence is already assigned to: %1").arg(used_action));
+    } else {
+        model->setData(index, default_key_sequence);
+    }
+}
+
+void ConfigureHotkeys::RestoreHotkey(QModelIndex index) {
+    const QKeySequence& default_key_sequence = QKeySequence::fromString(
+        Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
+    const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
+
+    if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) {
+        QMessageBox::warning(
+            this, tr("Conflicting Key Sequence"),
+            tr("The default key sequence is already assigned to: %1").arg(used_action));
+    } else {
+        model->setData(index, default_key_sequence.toString(QKeySequence::NativeText));
+    }
+}
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index a2ec3323e..f943ec538 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -7,6 +7,16 @@
 #include <memory>
 #include <QWidget>
 
+namespace Common {
+class ParamPackage;
+}
+
+namespace Core::HID {
+class HIDCore;
+class EmulatedController;
+enum class NpadButton : u64;
+} // namespace Core::HID
+
 namespace Ui {
 class ConfigureHotkeys;
 }
@@ -18,7 +28,7 @@ class ConfigureHotkeys : public QWidget {
     Q_OBJECT
 
 public:
-    explicit ConfigureHotkeys(QWidget* parent = nullptr);
+    explicit ConfigureHotkeys(Core::HID::HIDCore& hid_core_, QWidget* parent = nullptr);
     ~ConfigureHotkeys() override;
 
     void ApplyConfiguration(HotkeyRegistry& registry);
@@ -35,13 +45,24 @@ private:
     void RetranslateUI();
 
     void Configure(QModelIndex index);
+    void ConfigureController(QModelIndex index);
     std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const;
+    std::pair<bool, QString> IsUsedControllerKey(const QString& key_sequence) const;
 
     void RestoreDefaults();
     void ClearAll();
     void PopupContextMenu(const QPoint& menu_location);
+    void RestoreControllerHotkey(QModelIndex index);
+    void RestoreHotkey(QModelIndex index);
 
     std::unique_ptr<Ui::ConfigureHotkeys> ui;
 
     QStandardItemModel* model;
+
+    void SetPollingResult(Core::HID::NpadButton button, bool cancel);
+    QString GetButtonName(Core::HID::NpadButton button) const;
+    Core::HID::EmulatedController* controller;
+    std::unique_ptr<QTimer> timeout_timer;
+    std::unique_ptr<QTimer> poll_timer;
+    std::optional<std::function<void(Core::HID::NpadButton, bool)>> input_setter;
 };
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index e7e58f314..d96497c4e 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -2,10 +2,13 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <sstream>
 #include <QKeySequence>
 #include <QShortcut>
 #include <QTreeWidgetItem>
 #include <QtGlobal>
+
+#include "core/hid/emulated_controller.h"
 #include "yuzu/hotkeys.h"
 #include "yuzu/uisettings.h"
 
@@ -18,8 +21,9 @@ void HotkeyRegistry::SaveHotkeys() {
         for (const auto& hotkey : group.second) {
             UISettings::values.shortcuts.push_back(
                 {hotkey.first, group.first,
-                 UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
-                                                hotkey.second.context)});
+                 UISettings::ContextualShortcut({hotkey.second.keyseq.toString(),
+                                                 hotkey.second.controller_keyseq,
+                                                 hotkey.second.context})});
         }
     }
 }
@@ -29,28 +33,49 @@ void HotkeyRegistry::LoadHotkeys() {
     // beginGroup()
     for (auto shortcut : UISettings::values.shortcuts) {
         Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
-        if (!shortcut.shortcut.first.isEmpty()) {
-            hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
-            hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
+        if (!shortcut.shortcut.keyseq.isEmpty()) {
+            hk.keyseq =
+                QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText);
+            hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
+        }
+        if (!shortcut.shortcut.controller_keyseq.isEmpty()) {
+            hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
         }
         if (hk.shortcut) {
             hk.shortcut->disconnect();
             hk.shortcut->setKey(hk.keyseq);
         }
+        if (hk.controller_shortcut) {
+            hk.controller_shortcut->disconnect();
+            hk.controller_shortcut->SetKey(hk.controller_keyseq);
+        }
     }
 }
 
 QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
     Hotkey& hk = hotkey_groups[group][action];
 
-    if (!hk.shortcut)
+    if (!hk.shortcut) {
         hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context);
+    }
 
     hk.shortcut->setAutoRepeat(false);
 
     return hk.shortcut;
 }
 
+ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action,
+                                                        Core::HID::EmulatedController* controller) {
+    Hotkey& hk = hotkey_groups[group][action];
+
+    if (!hk.controller_shortcut) {
+        hk.controller_shortcut = new ControllerShortcut(controller);
+        hk.controller_shortcut->SetKey(hk.controller_keyseq);
+    }
+
+    return hk.controller_shortcut;
+}
+
 QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
     return hotkey_groups[group][action].keyseq;
 }
@@ -59,3 +84,131 @@ Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
                                                        const QString& action) {
     return hotkey_groups[group][action].context;
 }
+
+ControllerShortcut::ControllerShortcut(Core::HID::EmulatedController* controller) {
+    emulated_controller = controller;
+    Core::HID::ControllerUpdateCallback engine_callback{
+        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
+        .is_npad_service = false,
+    };
+    callback_key = emulated_controller->SetCallback(engine_callback);
+    is_enabled = true;
+}
+
+ControllerShortcut::~ControllerShortcut() {
+    emulated_controller->DeleteCallback(callback_key);
+}
+
+void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) {
+    button_sequence = buttons;
+}
+
+void ControllerShortcut::SetKey(const QString& buttons_shortcut) {
+    ControllerButtonSequence sequence{};
+    name = buttons_shortcut.toStdString();
+    std::istringstream command_line(buttons_shortcut.toStdString());
+    std::string line;
+    while (std::getline(command_line, line, '+')) {
+        if (line.empty()) {
+            continue;
+        }
+        if (line == "A") {
+            sequence.npad.a.Assign(1);
+        }
+        if (line == "B") {
+            sequence.npad.b.Assign(1);
+        }
+        if (line == "X") {
+            sequence.npad.x.Assign(1);
+        }
+        if (line == "Y") {
+            sequence.npad.y.Assign(1);
+        }
+        if (line == "L") {
+            sequence.npad.l.Assign(1);
+        }
+        if (line == "R") {
+            sequence.npad.r.Assign(1);
+        }
+        if (line == "ZL") {
+            sequence.npad.zl.Assign(1);
+        }
+        if (line == "ZR") {
+            sequence.npad.zr.Assign(1);
+        }
+        if (line == "Dpad_Left") {
+            sequence.npad.left.Assign(1);
+        }
+        if (line == "Dpad_Right") {
+            sequence.npad.right.Assign(1);
+        }
+        if (line == "Dpad_Up") {
+            sequence.npad.up.Assign(1);
+        }
+        if (line == "Dpad_Down") {
+            sequence.npad.down.Assign(1);
+        }
+        if (line == "Left_Stick") {
+            sequence.npad.stick_l.Assign(1);
+        }
+        if (line == "Right_Stick") {
+            sequence.npad.stick_r.Assign(1);
+        }
+        if (line == "Minus") {
+            sequence.npad.minus.Assign(1);
+        }
+        if (line == "Plus") {
+            sequence.npad.plus.Assign(1);
+        }
+        if (line == "Home") {
+            sequence.home.home.Assign(1);
+        }
+        if (line == "Screenshot") {
+            sequence.capture.capture.Assign(1);
+        }
+    }
+
+    button_sequence = sequence;
+}
+
+ControllerButtonSequence ControllerShortcut::ButtonSequence() const {
+    return button_sequence;
+}
+
+void ControllerShortcut::SetEnabled(bool enable) {
+    is_enabled = enable;
+}
+
+bool ControllerShortcut::IsEnabled() const {
+    return is_enabled;
+}
+
+void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
+    if (!is_enabled) {
+        return;
+    }
+    if (type != Core::HID::ControllerTriggerType::Button) {
+        return;
+    }
+    if (button_sequence.npad.raw == Core::HID::NpadButton::None &&
+        button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) {
+        return;
+    }
+
+    const auto player_npad_buttons =
+        emulated_controller->GetNpadButtons().raw & button_sequence.npad.raw;
+    const u64 player_capture_buttons =
+        emulated_controller->GetCaptureButtons().raw & button_sequence.capture.raw;
+    const u64 player_home_buttons =
+        emulated_controller->GetHomeButtons().raw & button_sequence.home.raw;
+
+    if (player_npad_buttons == button_sequence.npad.raw &&
+        player_capture_buttons == button_sequence.capture.raw &&
+        player_home_buttons == button_sequence.home.raw && !active) {
+        // Force user to press the home or capture button again
+        active = true;
+        emit Activated();
+        return;
+    }
+    active = false;
+}
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 248fadaf3..57a7c7da5 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -5,11 +5,53 @@
 #pragma once
 
 #include <map>
+#include "core/hid/hid_types.h"
 
 class QDialog;
 class QKeySequence;
 class QSettings;
 class QShortcut;
+class ControllerShortcut;
+
+namespace Core::HID {
+enum class ControllerTriggerType;
+class EmulatedController;
+} // namespace Core::HID
+
+struct ControllerButtonSequence {
+    Core::HID::CaptureButtonState capture{};
+    Core::HID::HomeButtonState home{};
+    Core::HID::NpadButtonState npad{};
+};
+
+class ControllerShortcut : public QObject {
+    Q_OBJECT
+
+public:
+    explicit ControllerShortcut(Core::HID::EmulatedController* controller);
+    ~ControllerShortcut();
+
+    void SetKey(const ControllerButtonSequence& buttons);
+    void SetKey(const QString& buttons_shortcut);
+
+    ControllerButtonSequence ButtonSequence() const;
+
+    void SetEnabled(bool enable);
+    bool IsEnabled() const;
+
+Q_SIGNALS:
+    void Activated();
+
+private:
+    void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
+
+    bool is_enabled{};
+    bool active{};
+    int callback_key{};
+    ControllerButtonSequence button_sequence{};
+    std::string name{};
+    Core::HID::EmulatedController* emulated_controller = nullptr;
+};
 
 class HotkeyRegistry final {
 public:
@@ -46,6 +88,8 @@ public:
      *          QShortcut's parent.
      */
     QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
+    ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action,
+                                            Core::HID::EmulatedController* controller);
 
     /**
      * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
@@ -68,7 +112,9 @@ public:
 private:
     struct Hotkey {
         QKeySequence keyseq;
+        QString controller_keyseq;
         QShortcut* shortcut = nullptr;
+        ControllerShortcut* controller_shortcut = nullptr;
         Qt::ShortcutContext context = Qt::WindowShortcut;
     };
 
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 53f11a9ac..e8a4ac918 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -32,6 +32,7 @@
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_oe.h"
 #include "core/hle/service/am/applets/applets.h"
+#include "yuzu/util/controller_navigation.h"
 
 // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
 // defines.
@@ -966,6 +967,12 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name
     action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name));
 
     this->addAction(action);
+
+    auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+    const auto* controller_hotkey =
+        hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
+    connect(controller_hotkey, &ControllerShortcut::Activated, this,
+            [action] { action->trigger(); });
 }
 
 void GMainWindow::InitializeHotkeys() {
@@ -987,8 +994,12 @@ void GMainWindow::InitializeHotkeys() {
 
     static const QString main_window = QStringLiteral("Main Window");
     const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {
-        const QShortcut* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
+        const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
+        auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+        const auto* controller_hotkey =
+            hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
         connect(hotkey, &QShortcut::activated, this, function);
+        connect(controller_hotkey, &ControllerShortcut::Activated, this, function);
     };
 
     connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] {
@@ -1165,8 +1176,7 @@ void GMainWindow::ConnectMenuEvents() {
     connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode);
     connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars);
     connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
-
-    connect(ui->action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
+    connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
 
     connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
     connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900);
@@ -2168,6 +2178,11 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
 }
 
 void GMainWindow::OnMenuLoadFile() {
+    if (is_load_file_select_active) {
+        return;
+    }
+
+    is_load_file_select_active = true;
     const QString extensions =
         QStringLiteral("*.")
             .append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
@@ -2177,6 +2192,7 @@ void GMainWindow::OnMenuLoadFile() {
                                     .arg(extensions);
     const QString filename = QFileDialog::getOpenFileName(
         this, tr("Load File"), UISettings::values.roms_path, file_filter);
+    is_load_file_select_active = false;
 
     if (filename.isEmpty()) {
         return;
@@ -2809,6 +2825,11 @@ void GMainWindow::OnTasStartStop() {
     if (!emulation_running) {
         return;
     }
+
+    // Disable system buttons to prevent TAS from executing a hotkey
+    auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+    controller->ResetSystemButtons();
+
     input_subsystem->GetTas()->StartStop();
     OnTasStateChanged();
 }
@@ -2817,12 +2838,34 @@ void GMainWindow::OnTasRecord() {
     if (!emulation_running) {
         return;
     }
+    if (is_tas_recording_dialog_active) {
+        return;
+    }
+
+    // Disable system buttons to prevent TAS from recording a hotkey
+    auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
+    controller->ResetSystemButtons();
+
     const bool is_recording = input_subsystem->GetTas()->Record();
     if (!is_recording) {
-        const auto res =
-            QMessageBox::question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
-                                  QMessageBox::Yes | QMessageBox::No);
+        is_tas_recording_dialog_active = true;
+        ControllerNavigation* controller_navigation =
+            new ControllerNavigation(system->HIDCore(), this);
+        // Use QMessageBox instead of question so we can link controller navigation
+        QMessageBox* box_dialog = new QMessageBox();
+        box_dialog->setWindowTitle(tr("TAS Recording"));
+        box_dialog->setText(tr("Overwrite file of player 1?"));
+        box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+        box_dialog->setDefaultButton(QMessageBox::Yes);
+        connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+                [box_dialog](Qt::Key key) {
+                    QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+                    QCoreApplication::postEvent(box_dialog, event);
+                });
+        int res = box_dialog->exec();
+        controller_navigation->UnloadController();
         input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
+        is_tas_recording_dialog_active = false;
     }
     OnTasStateChanged();
 }
@@ -2871,10 +2914,15 @@ void GMainWindow::OnLoadAmiibo() {
     if (emu_thread == nullptr || !emu_thread->IsRunning()) {
         return;
     }
+    if (is_amiibo_file_select_active) {
+        return;
+    }
 
+    is_amiibo_file_select_active = true;
     const QString extensions{QStringLiteral("*.bin")};
     const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
     const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
+    is_amiibo_file_select_active = false;
 
     if (filename.isEmpty()) {
         return;
@@ -2934,6 +2982,10 @@ void GMainWindow::OnToggleFilterBar() {
     }
 }
 
+void GMainWindow::OnToggleStatusBar() {
+    statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
+}
+
 void GMainWindow::OnCaptureScreenshot() {
     if (emu_thread == nullptr || !emu_thread->IsRunning()) {
         return;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7870bb963..ca4ab9af5 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -186,6 +186,9 @@ public slots:
     void OnTasStateChanged();
 
 private:
+    /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry.
+    void LinkActionShortcut(QAction* action, const QString& action_name);
+
     void RegisterMetaTypes();
 
     void InitializeWidgets();
@@ -286,6 +289,7 @@ private slots:
     void OnOpenYuzuFolder();
     void OnAbout();
     void OnToggleFilterBar();
+    void OnToggleStatusBar();
     void OnDisplayTitleBars(bool);
     void InitializeHotkeys();
     void ToggleFullscreen();
@@ -303,9 +307,6 @@ private slots:
     void OnMouseActivity();
 
 private:
-    /// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry.
-    void LinkActionShortcut(QAction* action, const QString& action_name);
-
     void RemoveBaseContent(u64 program_id, const QString& entry_type);
     void RemoveUpdateContent(u64 program_id, const QString& entry_type);
     void RemoveAddOnContent(u64 program_id, const QString& entry_type);
@@ -400,6 +401,16 @@ private:
 
     // Applets
     QtSoftwareKeyboardDialog* software_keyboard = nullptr;
+
+    // True if amiibo file select is visible
+    bool is_amiibo_file_select_active{};
+
+    // True if load file select is visible
+    bool is_load_file_select_active{};
+
+    // True if TAS recording dialog is visible
+    bool is_tas_recording_dialog_active{};
+
 #ifdef __linux__
     QDBusObjectPath wake_lock{};
 #endif
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index a610e7e25..402c4556d 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -17,7 +17,11 @@
 
 namespace UISettings {
 
-using ContextualShortcut = std::pair<QString, int>;
+struct ContextualShortcut {
+    QString keyseq;
+    QString controller_keyseq;
+    int context;
+};
 
 struct Shortcut {
     QString name;