From ceb65c259a8aeb3e5aabe8a94235073c9e1e1c80 Mon Sep 17 00:00:00 2001
From: grimkor <david@grimkor.tech>
Date: Fri, 28 Apr 2023 17:42:18 +0100
Subject: [PATCH] Allow fully customisable controller hotkeys

---
 src/yuzu/configuration/configure_hotkeys.cpp | 65 ++++++++++++--------
 src/yuzu/configuration/configure_hotkeys.h   |  2 +-
 src/yuzu/main.cpp                            | 18 ++++--
 src/yuzu/main.h                              |  3 +-
 4 files changed, 56 insertions(+), 32 deletions(-)

diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index daa77a8f82..0b2a965f8c 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -48,7 +48,9 @@ ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent
 
     connect(poll_timer.get(), &QTimer::timeout, [this] {
         const auto buttons = controller->GetNpadButtons();
-        if (buttons.raw != Core::HID::NpadButton::None) {
+        const auto home_pressed = controller->GetHomeButtons().home != 0;
+        const auto capture_pressed = controller->GetCaptureButtons().capture != 0;
+        if (home_pressed || capture_pressed) {
             SetPollingResult(buttons.raw, false);
             return;
         }
@@ -154,8 +156,10 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) {
             model->setData(index, previous_key);
             return;
         }
-
-        const QString button_string = tr("Home+%1").arg(GetButtonName(button));
+        const auto home_pressed = this->controller->GetHomeButtons().home != 0;
+        const auto capture_pressed = this->controller->GetCaptureButtons().capture != 0;
+        const QString button_string =
+            GetButtonCombinationName(button, home_pressed, capture_pressed);
 
         const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string);
 
@@ -174,72 +178,83 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) {
     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();
+    (*input_setter)(button, cancel);
     // Re-Enable configuration
     controller->EnableConfiguration();
-    controller->EnableSystemButtons();
-
-    (*input_setter)(button, cancel);
 
     input_setter = std::nullopt;
 }
 
-QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const {
+QString ConfigureHotkeys::GetButtonCombinationName(Core::HID::NpadButton button,
+                                                   const bool home = false,
+                                                   const bool capture = false) const {
     Core::HID::NpadButtonState state{button};
+    QString button_combination;
+    if (home) {
+        button_combination.append(QStringLiteral("Home+"));
+    }
+    if (capture) {
+        button_combination.append(QStringLiteral("Screenshot+"));
+    }
     if (state.a) {
-        return QStringLiteral("A");
+        button_combination.append(QStringLiteral("A+"));
     }
     if (state.b) {
-        return QStringLiteral("B");
+        button_combination.append(QStringLiteral("B+"));
     }
     if (state.x) {
-        return QStringLiteral("X");
+        button_combination.append(QStringLiteral("X+"));
     }
     if (state.y) {
-        return QStringLiteral("Y");
+        button_combination.append(QStringLiteral("Y+"));
     }
     if (state.l || state.right_sl || state.left_sl) {
-        return QStringLiteral("L");
+        button_combination.append(QStringLiteral("L+"));
     }
     if (state.r || state.right_sr || state.left_sr) {
-        return QStringLiteral("R");
+        button_combination.append(QStringLiteral("R+"));
     }
     if (state.zl) {
-        return QStringLiteral("ZL");
+        button_combination.append(QStringLiteral("ZL+"));
     }
     if (state.zr) {
-        return QStringLiteral("ZR");
+        button_combination.append(QStringLiteral("ZR+"));
     }
     if (state.left) {
-        return QStringLiteral("Dpad_Left");
+        button_combination.append(QStringLiteral("Dpad_Left+"));
     }
     if (state.right) {
-        return QStringLiteral("Dpad_Right");
+        button_combination.append(QStringLiteral("Dpad_Right+"));
     }
     if (state.up) {
-        return QStringLiteral("Dpad_Up");
+        button_combination.append(QStringLiteral("Dpad_Up+"));
     }
     if (state.down) {
-        return QStringLiteral("Dpad_Down");
+        button_combination.append(QStringLiteral("Dpad_Down+"));
     }
     if (state.stick_l) {
-        return QStringLiteral("Left_Stick");
+        button_combination.append(QStringLiteral("Left_Stick+"));
     }
     if (state.stick_r) {
-        return QStringLiteral("Right_Stick");
+        button_combination.append(QStringLiteral("Right_Stick+"));
     }
     if (state.minus) {
-        return QStringLiteral("Minus");
+        button_combination.append(QStringLiteral("Minus+"));
     }
     if (state.plus) {
-        return QStringLiteral("Plus");
+        button_combination.append(QStringLiteral("Plus+"));
+    }
+    if (button_combination.isEmpty()) {
+        return tr("Invalid");
+    } else {
+        button_combination.chop(1);
+        return button_combination;
     }
-    return tr("Invalid");
 }
 
 std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h
index e8e4143208..5fd1bcbfe7 100644
--- a/src/yuzu/configuration/configure_hotkeys.h
+++ b/src/yuzu/configuration/configure_hotkeys.h
@@ -59,7 +59,7 @@ private:
     QStandardItemModel* model;
 
     void SetPollingResult(Core::HID::NpadButton button, bool cancel);
-    QString GetButtonName(Core::HID::NpadButton button) const;
+    QString GetButtonCombinationName(Core::HID::NpadButton button, bool home, bool capture) const;
     Core::HID::EmulatedController* controller;
     std::unique_ptr<QTimer> timeout_timer;
     std::unique_ptr<QTimer> poll_timer;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b79409a68a..519a2906fc 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1163,7 +1163,8 @@ void GMainWindow::InitializeRecentFileMenuActions() {
     UpdateRecentFiles();
 }
 
-void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name) {
+void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name,
+                                     const bool tas_allowed) {
     static const QString main_window = QStringLiteral("Main Window");
     action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
     action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name));
@@ -1175,7 +1176,14 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name
     const auto* controller_hotkey =
         hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
     connect(
-        controller_hotkey, &ControllerShortcut::Activated, this, [action] { action->trigger(); },
+        controller_hotkey, &ControllerShortcut::Activated, this,
+        [action, tas_allowed, this] {
+            auto [tas_status, current_tas_frame, total_tas_frames] =
+                input_subsystem->GetTas()->GetStatus();
+            if (tas_allowed || tas_status == InputCommon::TasInput::TasState::Stopped) {
+                action->trigger();
+            }
+        },
         Qt::QueuedConnection);
 }
 
@@ -1192,9 +1200,9 @@ void GMainWindow::InitializeHotkeys() {
     LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
     LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen"));
     LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
-    LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"));
-    LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"));
-    LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"));
+    LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true);
+    LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true);
+    LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true);
 
     static const QString main_window = QStringLiteral("Main Window");
     const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8b5c1d7471..71d78a3db8 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -214,7 +214,8 @@ public slots:
 
 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 LinkActionShortcut(QAction* action, const QString& action_name,
+                            const bool tas_allowed = false);
 
     void RegisterMetaTypes();