summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar liamwhite2022-12-04 13:38:01 -0500
committerGravatar GitHub2022-12-04 13:38:01 -0500
commit522e7c5663bcb61a760c412d655295de11c38077 (patch)
treee76cacbb42a31d87eb014783a10597377a9898a4 /src
parentMerge pull request #9372 from liamwhite/vk12 (diff)
parentconfigure_input_player: Fix profile saving when using handheld controller type (diff)
downloadyuzu-522e7c5663bcb61a760c412d655295de11c38077.tar.gz
yuzu-522e7c5663bcb61a760c412d655295de11c38077.tar.xz
yuzu-522e7c5663bcb61a760c412d655295de11c38077.zip
Merge pull request #9273 from ameerj/per-game-profile
Configuration: Add per-game input profiles
Diffstat (limited to 'src')
-rw-r--r--src/common/settings_input.h1
-rw-r--r--src/core/hid/emulated_controller.cpp3
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/config.cpp73
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_input_per_game.cpp115
-rw-r--r--src/yuzu/configuration/configure_input_per_game.h45
-rw-r--r--src/yuzu/configuration/configure_input_per_game.ui333
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp8
-rw-r--r--src/yuzu/configuration/configure_input_player.h2
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp5
-rw-r--r--src/yuzu/configuration/configure_per_game.h6
-rw-r--r--src/yuzu/main.cpp18
13 files changed, 587 insertions, 27 deletions
diff --git a/src/common/settings_input.h b/src/common/settings_input.h
index 485e4ad22..46f38c703 100644
--- a/src/common/settings_input.h
+++ b/src/common/settings_input.h
@@ -391,6 +391,7 @@ struct PlayerInput {
391 u32 body_color_right; 391 u32 body_color_right;
392 u32 button_color_left; 392 u32 button_color_left;
393 u32 button_color_right; 393 u32 button_color_right;
394 std::string profile_name;
394}; 395};
395 396
396struct TouchscreenInput { 397struct TouchscreenInput {
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 9779378be..74c877728 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -110,10 +110,9 @@ void EmulatedController::ReloadFromSettings() {
110 original_npad_type = npad_type; 110 original_npad_type = npad_type;
111 } 111 }
112 112
113 Disconnect();
113 if (player.connected) { 114 if (player.connected) {
114 Connect(); 115 Connect();
115 } else {
116 Disconnect();
117 } 116 }
118 117
119 ReloadInput(); 118 ReloadInput();
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 656dd79a9..f192d6329 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -88,6 +88,9 @@ add_executable(yuzu
88 configuration/configure_input_advanced.cpp 88 configuration/configure_input_advanced.cpp
89 configuration/configure_input_advanced.h 89 configuration/configure_input_advanced.h
90 configuration/configure_input_advanced.ui 90 configuration/configure_input_advanced.ui
91 configuration/configure_input_per_game.cpp
92 configuration/configure_input_per_game.h
93 configuration/configure_input_per_game.ui
91 configuration/configure_input_player.cpp 94 configuration/configure_input_player.cpp
92 configuration/configure_input_player.h 95 configuration/configure_input_player.h
93 configuration/configure_input_player.ui 96 configuration/configure_input_player.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0c93df428..c11d1c8b3 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -124,6 +124,10 @@ void Config::Initialize(const std::string& config_name) {
124 } 124 }
125} 125}
126 126
127bool Config::IsCustomConfig() {
128 return type == ConfigType::PerGameConfig;
129}
130
127/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their 131/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their
128 * usages later in this file. This allows explicit definition of some types that don't work 132 * usages later in this file. This allows explicit definition of some types that don't work
129 * nicely with the general version. 133 * nicely with the general version.
@@ -194,8 +198,20 @@ void Config::ReadPlayerValue(std::size_t player_index) {
194 }(); 198 }();
195 199
196 auto& player = Settings::values.players.GetValue()[player_index]; 200 auto& player = Settings::values.players.GetValue()[player_index];
201 if (IsCustomConfig()) {
202 const auto profile_name =
203 qt_config->value(QStringLiteral("%1profile_name").arg(player_prefix), QString{})
204 .toString()
205 .toStdString();
206 if (profile_name.empty()) {
207 // Use the global input config
208 player = Settings::values.players.GetValue(true)[player_index];
209 return;
210 }
211 player.profile_name = profile_name;
212 }
197 213
198 if (player_prefix.isEmpty()) { 214 if (player_prefix.isEmpty() && Settings::IsConfiguringGlobal()) {
199 const auto controller = static_cast<Settings::ControllerType>( 215 const auto controller = static_cast<Settings::ControllerType>(
200 qt_config 216 qt_config
201 ->value(QStringLiteral("%1type").arg(player_prefix), 217 ->value(QStringLiteral("%1type").arg(player_prefix),
@@ -388,9 +404,26 @@ void Config::ReadAudioValues() {
388void Config::ReadControlValues() { 404void Config::ReadControlValues() {
389 qt_config->beginGroup(QStringLiteral("Controls")); 405 qt_config->beginGroup(QStringLiteral("Controls"));
390 406
407 Settings::values.players.SetGlobal(!IsCustomConfig());
391 for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { 408 for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
392 ReadPlayerValue(p); 409 ReadPlayerValue(p);
393 } 410 }
411 ReadGlobalSetting(Settings::values.use_docked_mode);
412
413 // Disable docked mode if handheld is selected
414 const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
415 if (controller_type == Settings::ControllerType::Handheld) {
416 Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
417 Settings::values.use_docked_mode.SetValue(false);
418 }
419
420 ReadGlobalSetting(Settings::values.vibration_enabled);
421 ReadGlobalSetting(Settings::values.enable_accurate_vibrations);
422 ReadGlobalSetting(Settings::values.motion_enabled);
423 if (IsCustomConfig()) {
424 qt_config->endGroup();
425 return;
426 }
394 ReadDebugValues(); 427 ReadDebugValues();
395 ReadKeyboardValues(); 428 ReadKeyboardValues();
396 ReadMouseValues(); 429 ReadMouseValues();
@@ -412,18 +445,6 @@ void Config::ReadControlValues() {
412 ReadBasicSetting(Settings::values.tas_loop); 445 ReadBasicSetting(Settings::values.tas_loop);
413 ReadBasicSetting(Settings::values.pause_tas_on_load); 446 ReadBasicSetting(Settings::values.pause_tas_on_load);
414 447
415 ReadGlobalSetting(Settings::values.use_docked_mode);
416
417 // Disable docked mode if handheld is selected
418 const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
419 if (controller_type == Settings::ControllerType::Handheld) {
420 Settings::values.use_docked_mode.SetValue(false);
421 }
422
423 ReadGlobalSetting(Settings::values.vibration_enabled);
424 ReadGlobalSetting(Settings::values.enable_accurate_vibrations);
425 ReadGlobalSetting(Settings::values.motion_enabled);
426
427 ReadBasicSetting(Settings::values.controller_navigation); 448 ReadBasicSetting(Settings::values.controller_navigation);
428 449
429 qt_config->endGroup(); 450 qt_config->endGroup();
@@ -905,7 +926,6 @@ void Config::ReadMultiplayerValues() {
905 926
906void Config::ReadValues() { 927void Config::ReadValues() {
907 if (global) { 928 if (global) {
908 ReadControlValues();
909 ReadDataStorageValues(); 929 ReadDataStorageValues();
910 ReadDebuggingValues(); 930 ReadDebuggingValues();
911 ReadDisabledAddOnValues(); 931 ReadDisabledAddOnValues();
@@ -914,6 +934,7 @@ void Config::ReadValues() {
914 ReadWebServiceValues(); 934 ReadWebServiceValues();
915 ReadMiscellaneousValues(); 935 ReadMiscellaneousValues();
916 } 936 }
937 ReadControlValues();
917 ReadCoreValues(); 938 ReadCoreValues();
918 ReadCpuValues(); 939 ReadCpuValues();
919 ReadRendererValues(); 940 ReadRendererValues();
@@ -932,12 +953,20 @@ void Config::SavePlayerValue(std::size_t player_index) {
932 }(); 953 }();
933 954
934 const auto& player = Settings::values.players.GetValue()[player_index]; 955 const auto& player = Settings::values.players.GetValue()[player_index];
956 if (IsCustomConfig()) {
957 if (player.profile_name.empty()) {
958 // No custom profile selected
959 return;
960 }
961 WriteSetting(QStringLiteral("%1profile_name").arg(player_prefix),
962 QString::fromStdString(player.profile_name), QString{});
963 }
935 964
936 WriteSetting(QStringLiteral("%1type").arg(player_prefix), 965 WriteSetting(QStringLiteral("%1type").arg(player_prefix),
937 static_cast<u8>(player.controller_type), 966 static_cast<u8>(player.controller_type),
938 static_cast<u8>(Settings::ControllerType::ProController)); 967 static_cast<u8>(Settings::ControllerType::ProController));
939 968
940 if (!player_prefix.isEmpty()) { 969 if (!player_prefix.isEmpty() || !Settings::IsConfiguringGlobal()) {
941 WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, 970 WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected,
942 player_index == 0); 971 player_index == 0);
943 WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix), 972 WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
@@ -1055,7 +1084,6 @@ void Config::SaveIrCameraValues() {
1055 1084
1056void Config::SaveValues() { 1085void Config::SaveValues() {
1057 if (global) { 1086 if (global) {
1058 SaveControlValues();
1059 SaveDataStorageValues(); 1087 SaveDataStorageValues();
1060 SaveDebuggingValues(); 1088 SaveDebuggingValues();
1061 SaveDisabledAddOnValues(); 1089 SaveDisabledAddOnValues();
@@ -1064,6 +1092,7 @@ void Config::SaveValues() {
1064 SaveWebServiceValues(); 1092 SaveWebServiceValues();
1065 SaveMiscellaneousValues(); 1093 SaveMiscellaneousValues();
1066 } 1094 }
1095 SaveControlValues();
1067 SaveCoreValues(); 1096 SaveCoreValues();
1068 SaveCpuValues(); 1097 SaveCpuValues();
1069 SaveRendererValues(); 1098 SaveRendererValues();
@@ -1088,9 +1117,14 @@ void Config::SaveAudioValues() {
1088void Config::SaveControlValues() { 1117void Config::SaveControlValues() {
1089 qt_config->beginGroup(QStringLiteral("Controls")); 1118 qt_config->beginGroup(QStringLiteral("Controls"));
1090 1119
1120 Settings::values.players.SetGlobal(!IsCustomConfig());
1091 for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { 1121 for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
1092 SavePlayerValue(p); 1122 SavePlayerValue(p);
1093 } 1123 }
1124 if (IsCustomConfig()) {
1125 qt_config->endGroup();
1126 return;
1127 }
1094 SaveDebugValues(); 1128 SaveDebugValues();
1095 SaveMouseValues(); 1129 SaveMouseValues();
1096 SaveTouchscreenValues(); 1130 SaveTouchscreenValues();
@@ -1579,6 +1613,13 @@ void Config::SaveControlPlayerValue(std::size_t player_index) {
1579 qt_config->endGroup(); 1613 qt_config->endGroup();
1580} 1614}
1581 1615
1616void Config::ClearControlPlayerValues() {
1617 qt_config->beginGroup(QStringLiteral("Controls"));
1618 // If key is an empty string, all keys in the current group() are removed.
1619 qt_config->remove(QString{});
1620 qt_config->endGroup();
1621}
1622
1582const std::string& Config::GetConfigFilePath() const { 1623const std::string& Config::GetConfigFilePath() const {
1583 return qt_config_loc; 1624 return qt_config_loc;
1584} 1625}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 06fa7d2d0..7d26e9ab6 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -34,6 +34,7 @@ public:
34 34
35 void ReadControlPlayerValue(std::size_t player_index); 35 void ReadControlPlayerValue(std::size_t player_index);
36 void SaveControlPlayerValue(std::size_t player_index); 36 void SaveControlPlayerValue(std::size_t player_index);
37 void ClearControlPlayerValues();
37 38
38 const std::string& GetConfigFilePath() const; 39 const std::string& GetConfigFilePath() const;
39 40
@@ -58,6 +59,7 @@ public:
58 59
59private: 60private:
60 void Initialize(const std::string& config_name); 61 void Initialize(const std::string& config_name);
62 bool IsCustomConfig();
61 63
62 void ReadValues(); 64 void ReadValues();
63 void ReadPlayerValue(std::size_t player_index); 65 void ReadPlayerValue(std::size_t player_index);
diff --git a/src/yuzu/configuration/configure_input_per_game.cpp b/src/yuzu/configuration/configure_input_per_game.cpp
new file mode 100644
index 000000000..78e65d468
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_per_game.cpp
@@ -0,0 +1,115 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/settings.h"
5#include "core/core.h"
6#include "core/hid/emulated_controller.h"
7#include "core/hid/hid_core.h"
8#include "ui_configure_input_per_game.h"
9#include "yuzu/configuration/config.h"
10#include "yuzu/configuration/configure_input_per_game.h"
11#include "yuzu/configuration/input_profiles.h"
12
13ConfigureInputPerGame::ConfigureInputPerGame(Core::System& system_, Config* config_,
14 QWidget* parent)
15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPerGame>()),
16 profiles(std::make_unique<InputProfiles>()), system{system_}, config{config_} {
17 ui->setupUi(this);
18 const std::array labels = {
19 ui->label_player_1, ui->label_player_2, ui->label_player_3, ui->label_player_4,
20 ui->label_player_5, ui->label_player_6, ui->label_player_7, ui->label_player_8,
21 };
22 profile_comboboxes = {
23 ui->profile_player_1, ui->profile_player_2, ui->profile_player_3, ui->profile_player_4,
24 ui->profile_player_5, ui->profile_player_6, ui->profile_player_7, ui->profile_player_8,
25 };
26
27 Settings::values.players.SetGlobal(false);
28
29 const auto& profile_names = profiles->GetInputProfileNames();
30 const auto populate_profiles = [this, &profile_names](size_t player_index) {
31 const auto previous_profile =
32 Settings::values.players.GetValue()[player_index].profile_name;
33
34 auto* const player_combobox = profile_comboboxes[player_index];
35 player_combobox->addItem(tr("Use global input configuration"));
36
37 for (size_t index = 0; index < profile_names.size(); ++index) {
38 const auto& profile_name = profile_names[index];
39 player_combobox->addItem(QString::fromStdString(profile_name));
40 if (profile_name == previous_profile) {
41 // offset by 1 since the first element is the global config
42 player_combobox->setCurrentIndex(static_cast<int>(index + 1));
43 }
44 }
45 };
46 for (size_t index = 0; index < profile_comboboxes.size(); ++index) {
47 labels[index]->setText(tr("Player %1 profile").arg(index + 1));
48 populate_profiles(index);
49 }
50
51 LoadConfiguration();
52}
53
54void ConfigureInputPerGame::ApplyConfiguration() {
55 LoadConfiguration();
56 SaveConfiguration();
57}
58
59void ConfigureInputPerGame::LoadConfiguration() {
60 static constexpr size_t HANDHELD_INDEX = 8;
61
62 auto& hid_core = system.HIDCore();
63 for (size_t player_index = 0; player_index < profile_comboboxes.size(); ++player_index) {
64 Settings::values.players.SetGlobal(false);
65
66 auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(player_index);
67 auto* const player_combobox = profile_comboboxes[player_index];
68
69 const auto selection_index = player_combobox->currentIndex();
70 if (selection_index == 0) {
71 Settings::values.players.GetValue()[player_index].profile_name = "";
72 if (player_index == 0) {
73 Settings::values.players.GetValue()[HANDHELD_INDEX] = {};
74 }
75 Settings::values.players.SetGlobal(true);
76 emulated_controller->ReloadFromSettings();
77 continue;
78 }
79 const auto profile_name = player_combobox->itemText(selection_index).toStdString();
80 if (profile_name.empty()) {
81 continue;
82 }
83 auto& player = Settings::values.players.GetValue()[player_index];
84 player.profile_name = profile_name;
85 // Read from the profile into the custom player settings
86 profiles->LoadProfile(profile_name, player_index);
87 // Make sure the controller is connected
88 player.connected = true;
89
90 emulated_controller->ReloadFromSettings();
91
92 if (player_index > 0) {
93 continue;
94 }
95 // Handle Handheld cases
96 auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX];
97 auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
98 if (player.controller_type == Settings::ControllerType::Handheld) {
99 handheld_player = player;
100 } else {
101 handheld_player = {};
102 }
103 handheld_controller->ReloadFromSettings();
104 }
105}
106
107void ConfigureInputPerGame::SaveConfiguration() {
108 Settings::values.players.SetGlobal(false);
109
110 // Clear all controls from the config in case the user reverted back to globals
111 config->ClearControlPlayerValues();
112 for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) {
113 config->SaveControlPlayerValue(index);
114 }
115}
diff --git a/src/yuzu/configuration/configure_input_per_game.h b/src/yuzu/configuration/configure_input_per_game.h
new file mode 100644
index 000000000..660faf574
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_per_game.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include <QWidget>
9
10#include "ui_configure_input_per_game.h"
11#include "yuzu/configuration/input_profiles.h"
12
13class QComboBox;
14
15namespace Core {
16class System;
17} // namespace Core
18
19class Config;
20
21class ConfigureInputPerGame : public QWidget {
22 Q_OBJECT
23
24public:
25 explicit ConfigureInputPerGame(Core::System& system_, Config* config_,
26 QWidget* parent = nullptr);
27
28 /// Load and Save configurations to settings file.
29 void ApplyConfiguration();
30
31private:
32 /// Load configuration from settings file.
33 void LoadConfiguration();
34
35 /// Save configuration to settings file.
36 void SaveConfiguration();
37
38 std::unique_ptr<Ui::ConfigureInputPerGame> ui;
39 std::unique_ptr<InputProfiles> profiles;
40
41 std::array<QComboBox*, 8> profile_comboboxes;
42
43 Core::System& system;
44 Config* config;
45};
diff --git a/src/yuzu/configuration/configure_input_per_game.ui b/src/yuzu/configuration/configure_input_per_game.ui
new file mode 100644
index 000000000..fbd8eab1c
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_per_game.ui
@@ -0,0 +1,333 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureInputPerGame</class>
4 <widget class="QWidget" name="PerGameInput">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>541</width>
10 <height>759</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <property name="accessibleName">
17 <string>Graphics</string>
18 </property>
19 <layout class="QVBoxLayout" name="verticalLayout_1">
20 <item>
21 <layout class="QVBoxLayout" name="verticalLayout_2">
22 <property name="spacing">
23 <number>0</number>
24 </property>
25 <item>
26 <widget class="QGroupBox" name="groupBox">
27 <property name="title">
28 <string>Input Profiles</string>
29 </property>
30 <layout class="QVBoxLayout" name="verticalLayout_4">
31 <item>
32 <widget class="QWidget" name="player_1" native="true">
33 <layout class="QHBoxLayout" name="input_profile_layout_1">
34 <property name="leftMargin">
35 <number>0</number>
36 </property>
37 <property name="topMargin">
38 <number>0</number>
39 </property>
40 <property name="rightMargin">
41 <number>0</number>
42 </property>
43 <property name="bottomMargin">
44 <number>0</number>
45 </property>
46 <item>
47 <widget class="QLabel" name="label_player_1">
48 <property name="text">
49 <string>Player 1 Profile</string>
50 </property>
51 </widget>
52 </item>
53 <item>
54 <widget class="QComboBox" name="profile_player_1">
55 <property name="sizePolicy">
56 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
57 <horstretch>0</horstretch>
58 <verstretch>0</verstretch>
59 </sizepolicy>
60 </property>
61 </widget>
62 </item>
63 </layout>
64 </widget>
65 </item>
66 <item>
67 <widget class="QWidget" name="player_2" native="true">
68 <layout class="QHBoxLayout" name="input_profile_layout_2">
69 <property name="leftMargin">
70 <number>0</number>
71 </property>
72 <property name="topMargin">
73 <number>0</number>
74 </property>
75 <property name="rightMargin">
76 <number>0</number>
77 </property>
78 <property name="bottomMargin">
79 <number>0</number>
80 </property>
81 <item>
82 <widget class="QLabel" name="label_player_2">
83 <property name="text">
84 <string>Player 2 Profile</string>
85 </property>
86 </widget>
87 </item>
88 <item>
89 <widget class="QComboBox" name="profile_player_2">
90 <property name="sizePolicy">
91 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
92 <horstretch>0</horstretch>
93 <verstretch>0</verstretch>
94 </sizepolicy>
95 </property>
96 </widget>
97 </item>
98 </layout>
99 </widget>
100 </item>
101 <item>
102 <widget class="QWidget" name="player_3" native="true">
103 <layout class="QHBoxLayout" name="input_profile_layout_3">
104 <property name="leftMargin">
105 <number>0</number>
106 </property>
107 <property name="topMargin">
108 <number>0</number>
109 </property>
110 <property name="rightMargin">
111 <number>0</number>
112 </property>
113 <property name="bottomMargin">
114 <number>0</number>
115 </property>
116 <item>
117 <widget class="QLabel" name="label_player_3">
118 <property name="text">
119 <string>Player 3 Profile</string>
120 </property>
121 </widget>
122 </item>
123 <item>
124 <widget class="QComboBox" name="profile_player_3">
125 <property name="sizePolicy">
126 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
127 <horstretch>0</horstretch>
128 <verstretch>0</verstretch>
129 </sizepolicy>
130 </property>
131 </widget>
132 </item>
133 </layout>
134 </widget>
135 </item>
136 <item>
137 <widget class="QWidget" name="player_4" native="true">
138 <layout class="QHBoxLayout" name="input_profile_layout_4">
139 <property name="leftMargin">
140 <number>0</number>
141 </property>
142 <property name="topMargin">
143 <number>0</number>
144 </property>
145 <property name="rightMargin">
146 <number>0</number>
147 </property>
148 <property name="bottomMargin">
149 <number>0</number>
150 </property>
151 <item>
152 <widget class="QLabel" name="label_player_4">
153 <property name="text">
154 <string>Player 4 Profile</string>
155 </property>
156 </widget>
157 </item>
158 <item>
159 <widget class="QComboBox" name="profile_player_4">
160 <property name="sizePolicy">
161 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
162 <horstretch>0</horstretch>
163 <verstretch>0</verstretch>
164 </sizepolicy>
165 </property>
166 </widget>
167 </item>
168 </layout>
169 </widget>
170 </item>
171 <item>
172 <widget class="QWidget" name="player_5" native="true">
173 <layout class="QHBoxLayout" name="input_profile_layout_5">
174 <property name="leftMargin">
175 <number>0</number>
176 </property>
177 <property name="topMargin">
178 <number>0</number>
179 </property>
180 <property name="rightMargin">
181 <number>0</number>
182 </property>
183 <property name="bottomMargin">
184 <number>0</number>
185 </property>
186 <item>
187 <widget class="QLabel" name="label_player_5">
188 <property name="text">
189 <string>Player 5 Profile</string>
190 </property>
191 </widget>
192 </item>
193 <item>
194 <widget class="QComboBox" name="profile_player_5">
195 <property name="sizePolicy">
196 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
197 <horstretch>0</horstretch>
198 <verstretch>0</verstretch>
199 </sizepolicy>
200 </property>
201 </widget>
202 </item>
203 </layout>
204 </widget>
205 </item>
206 <item>
207 <widget class="QWidget" name="player_6" native="true">
208 <layout class="QHBoxLayout" name="input_profile_layout_6">
209 <property name="leftMargin">
210 <number>0</number>
211 </property>
212 <property name="topMargin">
213 <number>0</number>
214 </property>
215 <property name="rightMargin">
216 <number>0</number>
217 </property>
218 <property name="bottomMargin">
219 <number>0</number>
220 </property>
221 <item>
222 <widget class="QLabel" name="label_player_6">
223 <property name="text">
224 <string>Player 6 Profile</string>
225 </property>
226 </widget>
227 </item>
228 <item>
229 <widget class="QComboBox" name="profile_player_6">
230 <property name="sizePolicy">
231 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
232 <horstretch>0</horstretch>
233 <verstretch>0</verstretch>
234 </sizepolicy>
235 </property>
236 </widget>
237 </item>
238 </layout>
239 </widget>
240 </item>
241 <item>
242 <widget class="QWidget" name="player_7" native="true">
243 <layout class="QHBoxLayout" name="input_profile_layout_7">
244 <property name="leftMargin">
245 <number>0</number>
246 </property>
247 <property name="topMargin">
248 <number>0</number>
249 </property>
250 <property name="rightMargin">
251 <number>0</number>
252 </property>
253 <property name="bottomMargin">
254 <number>0</number>
255 </property>
256 <item>
257 <widget class="QLabel" name="label_player_7">
258 <property name="text">
259 <string>Player 7 Profile</string>
260 </property>
261 </widget>
262 </item>
263 <item>
264 <widget class="QComboBox" name="profile_player_7">
265 <property name="sizePolicy">
266 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
267 <horstretch>0</horstretch>
268 <verstretch>0</verstretch>
269 </sizepolicy>
270 </property>
271 </widget>
272 </item>
273 </layout>
274 </widget>
275 </item>
276 <item>
277 <widget class="QWidget" name="player_8" native="true">
278 <layout class="QHBoxLayout" name="input_profile_layout_8">
279 <property name="leftMargin">
280 <number>0</number>
281 </property>
282 <property name="topMargin">
283 <number>0</number>
284 </property>
285 <property name="rightMargin">
286 <number>0</number>
287 </property>
288 <property name="bottomMargin">
289 <number>0</number>
290 </property>
291 <item>
292 <widget class="QLabel" name="label_player_8">
293 <property name="text">
294 <string>Player 8 Profile</string>
295 </property>
296 </widget>
297 </item>
298 <item>
299 <widget class="QComboBox" name="profile_player_8">
300 <property name="sizePolicy">
301 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
302 <horstretch>0</horstretch>
303 <verstretch>0</verstretch>
304 </sizepolicy>
305 </property>
306 </widget>
307 </item>
308 </layout>
309 </widget>
310 </item>
311 </layout>
312 </widget>
313 </item>
314 </layout>
315 </item>
316 <item>
317 <spacer name="verticalSpacer">
318 <property name="orientation">
319 <enum>Qt::Vertical</enum>
320 </property>
321 <property name="sizeHint" stdset="0">
322 <size>
323 <width>20</width>
324 <height>40</height>
325 </size>
326 </property>
327 </spacer>
328 </item>
329 </layout>
330 </widget>
331 <resources/>
332 <connections/>
333</ui>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 9e5a40fe7..ed21f4b92 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1553,6 +1553,7 @@ void ConfigureInputPlayer::LoadProfile() {
1553} 1553}
1554 1554
1555void ConfigureInputPlayer::SaveProfile() { 1555void ConfigureInputPlayer::SaveProfile() {
1556 static constexpr size_t HANDHELD_INDEX = 8;
1556 const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); 1557 const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
1557 1558
1558 if (profile_name.isEmpty()) { 1559 if (profile_name.isEmpty()) {
@@ -1561,7 +1562,12 @@ void ConfigureInputPlayer::SaveProfile() {
1561 1562
1562 ApplyConfiguration(); 1563 ApplyConfiguration();
1563 1564
1564 if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) { 1565 // When we're in handheld mode, only the handheld emulated controller bindings are updated
1566 const bool is_handheld = player_index == 0 && emulated_controller->GetNpadIdType() ==
1567 Core::HID::NpadIdType::Handheld;
1568 const auto profile_player_index = is_handheld ? HANDHELD_INDEX : player_index;
1569
1570 if (!profiles->SaveProfile(profile_name.toStdString(), profile_player_index)) {
1565 QMessageBox::critical(this, tr("Save Input Profile"), 1571 QMessageBox::critical(this, tr("Save Input Profile"),
1566 tr("Failed to save the input profile \"%1\"").arg(profile_name)); 1572 tr("Failed to save the input profile \"%1\"").arg(profile_name));
1567 UpdateInputProfiles(); 1573 UpdateInputProfiles();
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 79434fdd8..26f60d121 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -38,7 +38,7 @@ enum class InputType;
38 38
39namespace Ui { 39namespace Ui {
40class ConfigureInputPlayer; 40class ConfigureInputPlayer;
41} 41} // namespace Ui
42 42
43namespace Core::HID { 43namespace Core::HID {
44class HIDCore; 44class HIDCore;
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index c3cb8f61d..93db47cfd 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -28,7 +28,7 @@
28#include "yuzu/configuration/configure_general.h" 28#include "yuzu/configuration/configure_general.h"
29#include "yuzu/configuration/configure_graphics.h" 29#include "yuzu/configuration/configure_graphics.h"
30#include "yuzu/configuration/configure_graphics_advanced.h" 30#include "yuzu/configuration/configure_graphics_advanced.h"
31#include "yuzu/configuration/configure_input.h" 31#include "yuzu/configuration/configure_input_per_game.h"
32#include "yuzu/configuration/configure_per_game.h" 32#include "yuzu/configuration/configure_per_game.h"
33#include "yuzu/configuration/configure_per_game_addons.h" 33#include "yuzu/configuration/configure_per_game_addons.h"
34#include "yuzu/configuration/configure_system.h" 34#include "yuzu/configuration/configure_system.h"
@@ -50,6 +50,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
50 general_tab = std::make_unique<ConfigureGeneral>(system_, this); 50 general_tab = std::make_unique<ConfigureGeneral>(system_, this);
51 graphics_tab = std::make_unique<ConfigureGraphics>(system_, this); 51 graphics_tab = std::make_unique<ConfigureGraphics>(system_, this);
52 graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this); 52 graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this);
53 input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
53 system_tab = std::make_unique<ConfigureSystem>(system_, this); 54 system_tab = std::make_unique<ConfigureSystem>(system_, this);
54 55
55 ui->setupUi(this); 56 ui->setupUi(this);
@@ -61,6 +62,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
61 ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics")); 62 ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
62 ui->tabWidget->addTab(graphics_advanced_tab.get(), tr("Adv. Graphics")); 63 ui->tabWidget->addTab(graphics_advanced_tab.get(), tr("Adv. Graphics"));
63 ui->tabWidget->addTab(audio_tab.get(), tr("Audio")); 64 ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
65 ui->tabWidget->addTab(input_tab.get(), tr("Input Profiles"));
64 66
65 setFocusPolicy(Qt::ClickFocus); 67 setFocusPolicy(Qt::ClickFocus);
66 setWindowTitle(tr("Properties")); 68 setWindowTitle(tr("Properties"));
@@ -91,6 +93,7 @@ void ConfigurePerGame::ApplyConfiguration() {
91 graphics_tab->ApplyConfiguration(); 93 graphics_tab->ApplyConfiguration();
92 graphics_advanced_tab->ApplyConfiguration(); 94 graphics_advanced_tab->ApplyConfiguration();
93 audio_tab->ApplyConfiguration(); 95 audio_tab->ApplyConfiguration();
96 input_tab->ApplyConfiguration();
94 97
95 system.ApplySettings(); 98 system.ApplySettings();
96 Settings::LogSettings(); 99 Settings::LogSettings();
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 17a98a0f3..4ecc43541 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -16,12 +16,17 @@ namespace Core {
16class System; 16class System;
17} 17}
18 18
19namespace InputCommon {
20class InputSubsystem;
21}
22
19class ConfigurePerGameAddons; 23class ConfigurePerGameAddons;
20class ConfigureAudio; 24class ConfigureAudio;
21class ConfigureCpu; 25class ConfigureCpu;
22class ConfigureGeneral; 26class ConfigureGeneral;
23class ConfigureGraphics; 27class ConfigureGraphics;
24class ConfigureGraphicsAdvanced; 28class ConfigureGraphicsAdvanced;
29class ConfigureInputPerGame;
25class ConfigureSystem; 30class ConfigureSystem;
26 31
27class QGraphicsScene; 32class QGraphicsScene;
@@ -72,5 +77,6 @@ private:
72 std::unique_ptr<ConfigureGeneral> general_tab; 77 std::unique_ptr<ConfigureGeneral> general_tab;
73 std::unique_ptr<ConfigureGraphics> graphics_tab; 78 std::unique_ptr<ConfigureGraphics> graphics_tab;
74 std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab; 79 std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
80 std::unique_ptr<ConfigureInputPerGame> input_tab;
75 std::unique_ptr<ConfigureSystem> system_tab; 81 std::unique_ptr<ConfigureSystem> system_tab;
76}; 82};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e06ee7570..c0afb2e5f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -126,6 +126,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
126#include "yuzu/compatibility_list.h" 126#include "yuzu/compatibility_list.h"
127#include "yuzu/configuration/config.h" 127#include "yuzu/configuration/config.h"
128#include "yuzu/configuration/configure_dialog.h" 128#include "yuzu/configuration/configure_dialog.h"
129#include "yuzu/configuration/configure_input_per_game.h"
129#include "yuzu/debugger/console.h" 130#include "yuzu/debugger/console.h"
130#include "yuzu/debugger/controller.h" 131#include "yuzu/debugger/controller.h"
131#include "yuzu/debugger/profiler.h" 132#include "yuzu/debugger/profiler.h"
@@ -1658,6 +1659,11 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1658 LOG_INFO(Frontend, "yuzu starting..."); 1659 LOG_INFO(Frontend, "yuzu starting...");
1659 StoreRecentFile(filename); // Put the filename on top of the list 1660 StoreRecentFile(filename); // Put the filename on top of the list
1660 1661
1662 // Save configurations
1663 UpdateUISettings();
1664 game_list->SaveInterfaceLayout();
1665 config->Save();
1666
1661 u64 title_id{0}; 1667 u64 title_id{0};
1662 1668
1663 last_filename_booted = filename; 1669 last_filename_booted = filename;
@@ -1674,14 +1680,10 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1674 ? Common::FS::PathToUTF8String(file_path.filename()) 1680 ? Common::FS::PathToUTF8String(file_path.filename())
1675 : fmt::format("{:016X}", title_id); 1681 : fmt::format("{:016X}", title_id);
1676 Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); 1682 Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
1683 system->HIDCore().ReloadInputDevices();
1677 system->ApplySettings(); 1684 system->ApplySettings();
1678 } 1685 }
1679 1686
1680 // Save configurations
1681 UpdateUISettings();
1682 game_list->SaveInterfaceLayout();
1683 config->Save();
1684
1685 Settings::LogSettings(); 1687 Settings::LogSettings();
1686 1688
1687 if (UISettings::values.select_user_on_boot) { 1689 if (UISettings::values.select_user_on_boot) {
@@ -2802,6 +2804,7 @@ void GMainWindow::OnStopGame() {
2802 ShutdownGame(); 2804 ShutdownGame();
2803 2805
2804 Settings::RestoreGlobalState(system->IsPoweredOn()); 2806 Settings::RestoreGlobalState(system->IsPoweredOn());
2807 system->HIDCore().ReloadInputDevices();
2805 UpdateStatusButtons(); 2808 UpdateStatusButtons();
2806} 2809}
2807 2810
@@ -3281,6 +3284,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
3281 // Do not cause the global config to write local settings into the config file 3284 // Do not cause the global config to write local settings into the config file
3282 const bool is_powered_on = system->IsPoweredOn(); 3285 const bool is_powered_on = system->IsPoweredOn();
3283 Settings::RestoreGlobalState(is_powered_on); 3286 Settings::RestoreGlobalState(is_powered_on);
3287 system->HIDCore().ReloadInputDevices();
3284 3288
3285 UISettings::values.configuration_applied = false; 3289 UISettings::values.configuration_applied = false;
3286 3290
@@ -3764,6 +3768,7 @@ void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string detai
3764 ShutdownGame(); 3768 ShutdownGame();
3765 3769
3766 Settings::RestoreGlobalState(system->IsPoweredOn()); 3770 Settings::RestoreGlobalState(system->IsPoweredOn());
3771 system->HIDCore().ReloadInputDevices();
3767 UpdateStatusButtons(); 3772 UpdateStatusButtons();
3768 } 3773 }
3769 } else { 3774 } else {
@@ -3915,18 +3920,19 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
3915 // Unload controllers early 3920 // Unload controllers early
3916 controller_dialog->UnloadController(); 3921 controller_dialog->UnloadController();
3917 game_list->UnloadController(); 3922 game_list->UnloadController();
3918 system->HIDCore().UnloadInputDevices();
3919 3923
3920 // Shutdown session if the emu thread is active... 3924 // Shutdown session if the emu thread is active...
3921 if (emu_thread != nullptr) { 3925 if (emu_thread != nullptr) {
3922 ShutdownGame(); 3926 ShutdownGame();
3923 3927
3924 Settings::RestoreGlobalState(system->IsPoweredOn()); 3928 Settings::RestoreGlobalState(system->IsPoweredOn());
3929 system->HIDCore().ReloadInputDevices();
3925 UpdateStatusButtons(); 3930 UpdateStatusButtons();
3926 } 3931 }
3927 3932
3928 render_window->close(); 3933 render_window->close();
3929 multiplayer_state->Close(); 3934 multiplayer_state->Close();
3935 system->HIDCore().UnloadInputDevices();
3930 system->GetRoomNetwork().Shutdown(); 3936 system->GetRoomNetwork().Shutdown();
3931 3937
3932 QWidget::closeEvent(event); 3938 QWidget::closeEvent(event);