Просмотр исходного кода

Merge pull request #4866 from Morph1984/mjolnir-p3-prod

Project Mjölnir: Part 3 - Controller Profiles and Vibration Rework
bunnei 5 лет назад
Родитель
Сommit
abda366362
69 измененных файлов с 3328 добавлено и 1531 удалено
  1. 9 5
      dist/qt_themes/default/style.qss
  2. 38 48
      dist/qt_themes/qdarkstyle/style.qss
  3. 40 55
      dist/qt_themes/qdarkstyle_midnight_blue/style.qss
  4. 4 4
      src/core/frontend/applets/controller.cpp
  5. 1 1
      src/core/frontend/framebuffer_layout.cpp
  6. 8 1
      src/core/frontend/input.h
  7. 2 2
      src/core/hle/service/am/am.cpp
  8. 2 2
      src/core/hle/service/am/applets/controller.cpp
  9. 2 1
      src/core/hle/service/apm/controller.cpp
  10. 205 65
      src/core/hle/service/hid/controllers/npad.cpp
  11. 86 45
      src/core/hle/service/hid/controllers/npad.h
  12. 440 231
      src/core/hle/service/hid/hid.cpp
  13. 27 10
      src/core/hle/service/hid/hid.h
  14. 1 1
      src/core/hle/service/vi/vi.cpp
  15. 7 1
      src/core/settings.cpp
  16. 47 11
      src/core/settings.h
  17. 1 1
      src/core/telemetry_session.cpp
  18. 2 4
      src/input_common/gcadapter/gc_adapter.cpp
  19. 2 2
      src/input_common/gcadapter/gc_adapter.h
  20. 40 10
      src/input_common/gcadapter/gc_poller.cpp
  21. 11 0
      src/input_common/gcadapter/gc_poller.h
  22. 6 9
      src/input_common/main.cpp
  23. 71 67
      src/input_common/sdl/sdl_impl.cpp
  24. 2 0
      src/input_common/sdl/sdl_impl.h
  25. 14 7
      src/input_common/settings.cpp
  26. 28 7
      src/input_common/settings.h
  27. 1 1
      src/input_common/udp/client.cpp
  28. 8 3
      src/yuzu/CMakeLists.txt
  29. 0 20
      src/yuzu/aboutdialog.ui
  30. 95 59
      src/yuzu/applets/controller.cpp
  31. 14 5
      src/yuzu/applets/controller.h
  32. 15 34
      src/yuzu/applets/controller.ui
  33. 13 3
      src/yuzu/bootmanager.cpp
  34. 218 114
      src/yuzu/configuration/config.cpp
  35. 18 4
      src/yuzu/configuration/config.h
  36. 0 20
      src/yuzu/configuration/configure.ui
  37. 5 2
      src/yuzu/configuration/configure_debug_controller.cpp
  38. 6 3
      src/yuzu/configuration/configure_debug_controller.h
  39. 0 20
      src/yuzu/configuration/configure_debug_controller.ui
  40. 51 25
      src/yuzu/configuration/configure_input.cpp
  41. 9 5
      src/yuzu/configuration/configure_input.h
  42. 20 26
      src/yuzu/configuration/configure_input.ui
  43. 7 7
      src/yuzu/configuration/configure_input_advanced.cpp
  44. 1 1
      src/yuzu/configuration/configure_input_advanced.h
  45. 96 96
      src/yuzu/configuration/configure_input_advanced.ui
  46. 0 37
      src/yuzu/configuration/configure_input_dialog.cpp
  47. 0 38
      src/yuzu/configuration/configure_input_dialog.h
  48. 398 168
      src/yuzu/configuration/configure_input_player.cpp
  49. 40 9
      src/yuzu/configuration/configure_input_player.h
  50. 130 111
      src/yuzu/configuration/configure_input_player.ui
  51. 37 0
      src/yuzu/configuration/configure_input_profile_dialog.cpp
  52. 40 0
      src/yuzu/configuration/configure_input_profile_dialog.h
  53. 19 5
      src/yuzu/configuration/configure_input_profile_dialog.ui
  54. 0 10
      src/yuzu/configuration/configure_motion_touch.ui
  55. 13 33
      src/yuzu/configuration/configure_mouse_advanced.ui
  56. 2 1
      src/yuzu/configuration/configure_per_game.cpp
  57. 0 20
      src/yuzu/configuration/configure_per_game.ui
  58. 0 10
      src/yuzu/configuration/configure_touch_from_button.ui
  59. 1 21
      src/yuzu/configuration/configure_touchscreen_advanced.ui
  60. 146 0
      src/yuzu/configuration/configure_vibration.cpp
  61. 43 0
      src/yuzu/configuration/configure_vibration.h
  62. 546 0
      src/yuzu/configuration/configure_vibration.ui
  63. 131 0
      src/yuzu/configuration/input_profiles.cpp
  64. 32 0
      src/yuzu/configuration/input_profiles.h
  65. 45 11
      src/yuzu/main.cpp
  66. 1 0
      src/yuzu/main.h
  67. 15 12
      src/yuzu_cmd/config.cpp
  68. 8 0
      src/yuzu_cmd/default_ini.h
  69. 8 7
      src/yuzu_tester/config.cpp

+ 9 - 5
dist/qt_themes/default/style.qss

@@ -1,3 +1,7 @@
+QAbstractSpinBox {
+    min-height: 19px;
+}
+
 QPushButton#TogglableStatusBarButton {
     color: #959595;
     border: 1px solid transparent;
@@ -35,10 +39,10 @@ QPushButton#RendererStatusBarButton:!checked {
 }
 
 QPushButton#buttonRefreshDevices {
-    min-width: 20px;
-    min-height: 20px;
-    max-width: 20px;
-    max-height: 20px;
+    min-width: 21px;
+    min-height: 21px;
+    max-width: 21px;
+    max-height: 21px;
 }
 
 QWidget#bottomPerGameInput,
@@ -71,7 +75,7 @@ QWidget#middleControllerApplet {
 
 QWidget#topPerGameInput QComboBox,
 QWidget#middleControllerApplet QComboBox {
-    width: 123px;
+    width: 120px;
 }
 
 QWidget#connectedControllers {

+ 38 - 48
dist/qt_themes/qdarkstyle/style.qss

@@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled {
 }
 
 QRadioButton {
-    spacing: 5px;
-    outline: none;
     color: #eff0f1;
+    spacing: 3px;
+    padding: 0px;
+    border: none;
+    outline: none;
     margin-bottom: 2px;
 }
 
+QGroupBox QRadioButton {
+    padding-left: 0px;
+    padding-right: 7px;
+}
+
 QRadioButton:disabled {
     color: #76797C;
 }
@@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button {
 
 QPushButton {
     color: #eff0f1;
-    border-width: 1px;
-    border-color: #54575B;
-    border-style: solid;
-    padding: 6px 4px;
+    border: 1px solid #54575B;
     border-radius: 2px;
+    padding: 5px 0px 5px 0px;
     outline: none;
     min-width: 100px;
+    min-height: 13px;
     background-color: #232629;
 }
 
@@ -553,8 +559,9 @@ QComboBox {
     selection-background-color: #3daee9;
     border: 1px solid #54575B;
     border-radius: 2px;
-    padding: 4px 6px;
-    min-width: 75px;
+    padding: 0px 4px 0px 4px;
+    min-width: 60px;
+    min-height: 23px;
     background-color: #232629;
 }
 
@@ -608,26 +615,26 @@ QComboBox::down-arrow:focus {
 }
 
 QAbstractSpinBox {
-    padding: 4px 6px;
     border: 1px solid #54575B;
     background-color: #232629;
     color: #eff0f1;
     border-radius: 2px;
-    min-width: 75px;
+    min-width: 52px;
+    min-height: 23px;
 }
 
 QAbstractSpinBox:up-button {
     background-color: transparent;
     subcontrol-origin: border;
     subcontrol-position: center right;
-    left: -6px;
+    left: -2px;
 }
 
 QAbstractSpinBox:down-button {
     background-color: transparent;
     subcontrol-origin: border;
     subcontrol-position: center left;
-    right: -6px;
+    right: -2px;
 }
 
 QAbstractSpinBox::up-arrow,
@@ -1277,41 +1284,33 @@ QPushButton#RendererStatusBarButton:!checked {
 }
 
 QPushButton#buttonRefreshDevices {
-    min-width: 24px;
-    min-height: 24px;
-    max-width: 24px;
-    max-height: 24px;
+    min-width: 23px;
+    min-height: 23px;
+    max-width: 23px;
+    max-height: 23px;
     padding: 0px 0px;
 }
 
 QSpinBox#spinboxLStickRange,
-QSpinBox#spinboxRStickRange {
-    padding: 4px 0px 5px 0px;
-    min-width: 63px;
-}
-
-QSpinBox#vibrationSpin {
-    padding: 4px 0px 5px 0px;
-    min-width: 63px;
-}
-
-QSpinBox#spinboxLStickRange:up-button,
-QSpinBox#spinboxRStickRange:up-button,
-QSpinBox#vibrationSpin:up-button {
-    left: -2px;
-}
-
-QSpinBox#spinboxLStickRange:down-button,
-QSpinBox#spinboxRStickRange:down-button,
-QSpinBox#vibrationSpin:down-button {
-    right: -1px;
-}
-
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+    min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
 QGroupBox#motionGroup::indicator,
 QGroupBox#vibrationGroup::indicator {
     margin-left: 0px;
 }
 
+QDialog#ConfigureVibration QGroupBox::title,
 QGroupBox#motionGroup::title,
 QGroupBox#vibrationGroup::title {
     spacing: 2px;
@@ -1340,16 +1339,7 @@ QWidget#middleControllerApplet {
 
 QWidget#topPerGameInput QComboBox,
 QWidget#middleControllerApplet QComboBox {
-    width: 119px;
-}
-
-QRadioButton#radioDocked {
-    margin-left: -3px;
-}
-
-
-QRadioButton#radioUndocked {
-    margin-right: 5px;
+    width: 120px;
 }
 
 QWidget#connectedControllers {

+ 40 - 55
dist/qt_themes/qdarkstyle_midnight_blue/style.qss

@@ -172,8 +172,8 @@ QCheckBox {
   color: #F0F0F0;
   spacing: 4px;
   outline: none;
-  padding-top: 4px;
-  padding-bottom: 4px;
+  padding-top: 2px;
+  padding-bottom: 2px;
 }
 
 QCheckBox:focus {
@@ -239,7 +239,7 @@ QGroupBox {
   border: 1px solid #32414B;
   border-radius: 4px;
   margin-top: 12px;
-  padding: 4px;
+  padding: 2px;
 }
 
 QGroupBox::title {
@@ -247,7 +247,7 @@ QGroupBox::title {
   subcontrol-position: top left;
   padding-left: 3px;
   padding-right: 5px;
-  padding-top: 4px;
+  padding-top: 2px;
 }
 
 QGroupBox::indicator {
@@ -298,6 +298,11 @@ QRadioButton {
   outline: none;
 }
 
+QGroupBox QRadioButton {
+  padding-left: 0px;
+  padding-right: 7px;
+}
+
 QRadioButton:focus {
   border: none;
 }
@@ -321,7 +326,6 @@ QRadioButton QWidget {
 QRadioButton::indicator {
   border: none;
   outline: none;
-  margin-left: 4px;
   height: 16px;
   width: 16px;
 }
@@ -785,14 +789,8 @@ QAbstractSpinBox {
   background-color: #19232D;
   border: 1px solid #32414B;
   color: #F0F0F0;
-  /* This fixes 103, 111 */
-  padding-top: 2px;
-  /* This fixes 103, 111 */
-  padding-bottom: 2px;
-  padding-left: 4px;
-  padding-right: 4px;
   border-radius: 4px;
-  /* min-width: 5px; removed to fix 109 */
+  min-height: 19px;
 }
 
 QAbstractSpinBox:up-button {
@@ -997,10 +995,11 @@ QPushButton {
   border: 1px solid #32414B;
   color: #F0F0F0;
   border-radius: 4px;
-  padding: 3px;
+  padding: 3px 0px 3px 0px;
   outline: none;
   /* Issue #194 - Special case of QPushButton inside dialogs, for better UI */
   min-width: 80px;
+  min-height: 13px;
 }
 
 QPushButton:disabled {
@@ -1008,14 +1007,14 @@ QPushButton:disabled {
   border: 1px solid #32414B;
   color: #787878;
   border-radius: 4px;
-  padding: 3px;
+  padding: 3px 0px 3px 0px;
 }
 
 QPushButton:checked {
   background-color: #32414B;
   border: 1px solid #32414B;
   border-radius: 4px;
-  padding: 3px;
+  padding: 3px 0px 3px 0px;
   outline: none;
 }
 
@@ -1024,7 +1023,7 @@ QPushButton:checked:disabled {
   border: 1px solid #32414B;
   color: #787878;
   border-radius: 4px;
-  padding: 3px;
+  padding: 3px 0px 3px 0px;
   outline: none;
 }
 
@@ -1197,15 +1196,9 @@ QComboBox {
   border: 1px solid #32414B;
   border-radius: 4px;
   selection-background-color: #1464A0;
-  padding-left: 4px;
-  padding-right: 36px;
-  /* 4 + 16*2 See scrollbar size */
-  /* Fixes #103, #111 */
-  min-height: 1.5em;
-  /* padding-top: 2px;     removed to fix #132 */
-  /* padding-bottom: 2px;  removed to fix #132 */
-  /* min-width: 75px;      removed to fix #109 */
-  /* Needed to remove indicator - fix #132 */
+  padding: 0px 4px 0px 4px;
+  min-width: 60px;
+  min-height: 19px;
 }
 
 QComboBox QAbstractItemView {
@@ -2198,29 +2191,40 @@ QPushButton#RendererStatusBarButton:!checked {
 }
 
 QPushButton#buttonRefreshDevices {
-  min-width: 20px;
-  min-height: 20px;
-  max-width: 20px;
-  max-height: 20px;
+  min-width: 19px;
+  min-height: 19px;
+  max-width: 19px;
+  max-height: 19px;
   padding: 0px 0px;
 }
 
 QSpinBox#spinboxLStickRange,
-QSpinBox#spinboxRStickRange {
-  min-width: 38px;
-}
-
+QSpinBox#spinboxRStickRange,
+QSpinBox#vibrationSpinPlayer1,
+QSpinBox#vibrationSpinPlayer2,
+QSpinBox#vibrationSpinPlayer3,
+QSpinBox#vibrationSpinPlayer4,
+QSpinBox#vibrationSpinPlayer5,
+QSpinBox#vibrationSpinPlayer6,
+QSpinBox#vibrationSpinPlayer7,
+QSpinBox#vibrationSpinPlayer8 {
+  min-width: 68px;
+}
+
+QDialog#ConfigureVibration QGroupBox::indicator,
 QGroupBox#motionGroup::indicator,
 QGroupBox#vibrationGroup::indicator {
   margin-left: 0px;
 }
 
+QDialog#ConfigureVibration QGroupBox,
 QWidget#bottomPerGameInput QGroupBox#motionGroup,
 QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
 QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
   padding: 0px;
 }
 
+QDialog#ConfigureVibration QGroupBox::title,
 QGroupBox#motionGroup::title,
 QGroupBox#vibrationGroup::title {
   spacing: 2px;
@@ -2260,26 +2264,7 @@ QWidget#middleControllerApplet {
 
 QWidget#topPerGameInput QComboBox,
 QWidget#middleControllerApplet QComboBox {
-  padding-right: 2px;
-  width: 127px;
-}
-
-QGroupBox#handheldGroup {
-  padding-left: 0px;
-}
-
-QRadioButton#radioDocked {
-  margin-left: -1px;
-  padding-left: 0px;
-}
-
-QRadioButton#radioDocked::indicator {
-  margin-left: 0px;
-}
-
-
-QRadioButton#radioUndocked {
-  margin-right: 2px;
+  width: 120px;
 }
 
 QWidget#connectedControllers {
@@ -2352,7 +2337,7 @@ QCheckBox#checkboxPlayer5Connected,
 QCheckBox#checkboxPlayer6Connected,
 QCheckBox#checkboxPlayer7Connected,
 QCheckBox#checkboxPlayer8Connected {
-    spacing: 0px;
+  spacing: 0px;
 }
 
 QWidget#connectedControllers QLabel {
@@ -2427,7 +2412,7 @@ QCheckBox#checkboxPlayer7Connected::indicator,
 QCheckBox#checkboxPlayer8Connected::indicator {
   width: 14px;
   height: 14px;
-  margin-left: 2px;
+  margin-left: 0px;
 }
 
 QWidget#Player1LEDs QCheckBox::indicator:checked,

+ 4 - 4
src/core/frontend/applets/controller.cpp

@@ -27,19 +27,19 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
             ->GetAppletResource()
             ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
 
-    auto& players = Settings::values.players;
+    auto& players = Settings::values.players.GetValue();
 
     const std::size_t min_supported_players =
         parameters.enable_single_mode ? 1 : parameters.min_players;
 
     // Disconnect Handheld first.
-    npad.DisconnectNPadAtIndex(8);
+    npad.DisconnectNpadAtIndex(8);
 
     // Deduce the best configuration based on the input parameters.
     for (std::size_t index = 0; index < players.size() - 2; ++index) {
         // First, disconnect all controllers regardless of the value of keep_controllers_connected.
         // This makes it easy to connect the desired controllers.
-        npad.DisconnectNPadAtIndex(index);
+        npad.DisconnectNpadAtIndex(index);
 
         // Only connect the minimum number of required players.
         if (index >= min_supported_players) {
@@ -66,7 +66,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
                     npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
             }
         } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
-                   !Settings::values.use_docked_mode) {
+                   !Settings::values.use_docked_mode.GetValue()) {
             // We should *never* reach here under any normal circumstances.
             npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
                                     index);

+ 1 - 1
src/core/frontend/framebuffer_layout.cpp

@@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
 FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
     u32 width, height;
 
-    if (Settings::values.use_docked_mode) {
+    if (Settings::values.use_docked_mode.GetValue()) {
         width = ScreenDocked::Width * res_scale;
         height = ScreenDocked::Height * res_scale;
     } else {

+ 8 - 1
src/core/frontend/input.h

@@ -33,7 +33,7 @@ public:
     virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
         return {};
     }
-    virtual bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const {
+    virtual bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const {
         return {};
     }
 };
@@ -121,6 +121,13 @@ using ButtonDevice = InputDevice<bool>;
  */
 using AnalogDevice = InputDevice<std::tuple<float, float>>;
 
+/**
+ * A vibration device is an input device that returns an unsigned byte as status.
+ * It represents whether the vibration device supports vibration or not.
+ * If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
+ */
+using VibrationDevice = InputDevice<u8>;
+
 /**
  * A motion status is an object that returns a tuple of accelerometer state vector,
  * gyroscope state vector, rotation state vector and orientation state matrix.

+ 2 - 2
src/core/hle/service/am/am.cpp

@@ -751,7 +751,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
     IPC::ResponseBuilder rb{ctx, 4};
     rb.Push(RESULT_SUCCESS);
 
-    if (Settings::values.use_docked_mode) {
+    if (Settings::values.use_docked_mode.GetValue()) {
         rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
                 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
         rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
@@ -824,7 +824,7 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) {
 }
 
 void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
-    const bool use_docked_mode{Settings::values.use_docked_mode};
+    const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
     LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
 
     IPC::ResponseBuilder rb{ctx, 3};

+ 2 - 2
src/core/hle/service/am/applets/controller.cpp

@@ -25,7 +25,7 @@ namespace Service::AM::Applets {
 static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
     ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
     std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
-    HID::Controller_NPad::NPadType npad_style_set;
+    HID::Controller_NPad::NpadStyleSet npad_style_set;
     npad_style_set.raw = private_arg.style_set;
 
     return {
@@ -222,7 +222,7 @@ void Controller::Execute() {
 void Controller::ConfigurationComplete() {
     ControllerSupportResultInfo result_info{};
 
-    const auto& players = Settings::values.players;
+    const auto& players = Settings::values.players.GetValue();
 
     // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
     // Otherwise, only count connected players from P1-P8.

+ 2 - 1
src/core/hle/service/apm/controller.cpp

@@ -69,7 +69,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
 }
 
 PerformanceMode Controller::GetCurrentPerformanceMode() const {
-    return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld;
+    return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
+                                                       : PerformanceMode::Handheld;
 }
 
 PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {

+ 205 - 65
src/core/hle/service/hid/controllers/npad.cpp

@@ -117,7 +117,10 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
 }
 
 Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
-Controller_NPad::~Controller_NPad() = default;
+
+Controller_NPad::~Controller_NPad() {
+    OnRelease();
+}
 
 void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
     const auto controller_type = connected_controllers[controller_idx].type;
@@ -139,7 +142,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
         controller.properties.is_vertical.Assign(1);
         controller.properties.use_plus.Assign(1);
         controller.properties.use_minus.Assign(1);
-        controller.pad_assignment = NPadAssignments::Single;
+        controller.pad_assignment = NpadAssignments::Single;
         break;
     case NPadControllerType::Handheld:
         controller.joy_styles.handheld.Assign(1);
@@ -147,7 +150,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
         controller.properties.is_vertical.Assign(1);
         controller.properties.use_plus.Assign(1);
         controller.properties.use_minus.Assign(1);
-        controller.pad_assignment = NPadAssignments::Dual;
+        controller.pad_assignment = NpadAssignments::Dual;
         break;
     case NPadControllerType::JoyDual:
         controller.joy_styles.joycon_dual.Assign(1);
@@ -156,26 +159,26 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
         controller.properties.is_vertical.Assign(1);
         controller.properties.use_plus.Assign(1);
         controller.properties.use_minus.Assign(1);
-        controller.pad_assignment = NPadAssignments::Dual;
+        controller.pad_assignment = NpadAssignments::Dual;
         break;
     case NPadControllerType::JoyLeft:
         controller.joy_styles.joycon_left.Assign(1);
         controller.device_type.joycon_left.Assign(1);
         controller.properties.is_horizontal.Assign(1);
         controller.properties.use_minus.Assign(1);
-        controller.pad_assignment = NPadAssignments::Single;
+        controller.pad_assignment = NpadAssignments::Single;
         break;
     case NPadControllerType::JoyRight:
         controller.joy_styles.joycon_right.Assign(1);
         controller.device_type.joycon_right.Assign(1);
         controller.properties.is_horizontal.Assign(1);
         controller.properties.use_plus.Assign(1);
-        controller.pad_assignment = NPadAssignments::Single;
+        controller.pad_assignment = NpadAssignments::Single;
         break;
     case NPadControllerType::Pokeball:
         controller.joy_styles.pokeball.Assign(1);
         controller.device_type.pokeball.Assign(1);
-        controller.pad_assignment = NPadAssignments::Single;
+        controller.pad_assignment = NpadAssignments::Single;
         break;
     }
 
@@ -184,11 +187,14 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
     controller.single_color.button_color = 0;
 
     controller.dual_color_error = ColorReadError::ReadOk;
-    controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left;
-    controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left;
-    controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right;
+    controller.left_color.body_color =
+        Settings::values.players.GetValue()[controller_idx].body_color_left;
+    controller.left_color.button_color =
+        Settings::values.players.GetValue()[controller_idx].button_color_left;
+    controller.right_color.body_color =
+        Settings::values.players.GetValue()[controller_idx].body_color_right;
     controller.right_color.button_color =
-        Settings::values.players[controller_idx].button_color_right;
+        Settings::values.players.GetValue()[controller_idx].button_color_right;
 
     controller.battery_level[0] = BATTERY_FULL;
     controller.battery_level[1] = BATTERY_FULL;
@@ -199,7 +205,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
 
 void Controller_NPad::OnInit() {
     auto& kernel = system.Kernel();
-    for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
+    for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) {
         styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
             kernel, fmt::format("npad:NpadStyleSetChanged_{}", i));
     }
@@ -208,6 +214,8 @@ void Controller_NPad::OnInit() {
         return;
     }
 
+    OnLoadInputDevices();
+
     if (style.raw == 0) {
         // We want to support all controllers
         style.handheld.Assign(1);
@@ -218,12 +226,27 @@ void Controller_NPad::OnInit() {
         style.pokeball.Assign(1);
     }
 
-    std::transform(Settings::values.players.begin(), Settings::values.players.end(),
-                   connected_controllers.begin(), [](const Settings::PlayerInput& player) {
+    std::transform(Settings::values.players.GetValue().begin(),
+                   Settings::values.players.GetValue().end(), connected_controllers.begin(),
+                   [](const Settings::PlayerInput& player) {
                        return ControllerHolder{MapSettingsTypeToNPad(player.controller_type),
                                                player.connected};
                    });
 
+    // Connect the Player 1 or Handheld controller if none are connected.
+    if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
+                     [](const ControllerHolder& controller) { return controller.is_connected; })) {
+        const auto controller =
+            MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type);
+        if (controller == NPadControllerType::Handheld) {
+            Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
+            connected_controllers[HANDHELD_INDEX] = {controller, true};
+        } else {
+            Settings::values.players.GetValue()[0].connected = true;
+            connected_controllers[0] = {controller, true};
+        }
+    }
+
     // Account for handheld
     if (connected_controllers[HANDHELD_INDEX].is_connected) {
         connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld;
@@ -242,7 +265,7 @@ void Controller_NPad::OnInit() {
 }
 
 void Controller_NPad::OnLoadInputDevices() {
-    const auto& players = Settings::values.players;
+    const auto& players = Settings::values.players.GetValue();
     for (std::size_t i = 0; i < players.size(); ++i) {
         std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
                        players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
@@ -250,13 +273,26 @@ void Controller_NPad::OnLoadInputDevices() {
         std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
                        players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
                        sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
+        std::transform(players[i].vibrations.begin() +
+                           Settings::NativeVibration::VIBRATION_HID_BEGIN,
+                       players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END,
+                       vibrations[i].begin(), Input::CreateDevice<Input::VibrationDevice>);
         std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
                        players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
                        motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
+        for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) {
+            InitializeVibrationDeviceAtIndex(i, device_idx);
+        }
     }
 }
 
-void Controller_NPad::OnRelease() {}
+void Controller_NPad::OnRelease() {
+    for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) {
+        for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) {
+            VibrateControllerAtIndex(npad_idx, device_idx, {});
+        }
+    }
+}
 
 void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
     const auto controller_idx = NPadIdToIndex(npad_id);
@@ -339,7 +375,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
     if (!IsControllerActivated()) {
         return;
     }
-    for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+    for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
         auto& npad = shared_memory_entries[i];
         const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
                                                            &npad.handheld_states,
@@ -481,7 +517,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
     if (!IsControllerActivated()) {
         return;
     }
-    for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+    for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
         auto& npad = shared_memory_entries[i];
 
         const auto& controller_type = connected_controllers[i].type;
@@ -515,7 +551,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
         // Try to read sixaxis sensor states
         std::array<MotionDevice, 2> motion_devices;
 
-        if (sixaxis_sensors_enabled && Settings::values.motion_enabled) {
+        if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) {
             sixaxis_at_rest = true;
             for (std::size_t e = 0; e < motion_devices.size(); ++e) {
                 const auto& device = motions[i][e];
@@ -601,15 +637,15 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
                 shared_memory_entries.size() * sizeof(NPadEntry));
 }
 
-void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
+void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) {
     style.raw = style_set.raw;
 }
 
-Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
+Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const {
     return style;
 }
 
-void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
+void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) {
     ASSERT(length > 0 && (length % sizeof(u32)) == 0);
     supported_npad_id_types.clear();
     supported_npad_id_types.resize(length / sizeof(u32));
@@ -621,7 +657,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length)
     std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
 }
 
-std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
+std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
     return supported_npad_id_types.size();
 }
 
@@ -641,7 +677,7 @@ Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActi
     return handheld_activation_mode;
 }
 
-void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
+void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) {
     const std::size_t npad_index = NPadIdToIndex(npad_id);
     ASSERT(npad_index < shared_memory_entries.size());
     if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
@@ -649,35 +685,140 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
     }
 }
 
-void Controller_NPad::VibrateController(const std::vector<u32>& controllers,
-                                        const std::vector<Vibration>& vibrations) {
-    LOG_TRACE(Service_HID, "called");
-
-    if (!Settings::values.vibration_enabled || !can_controllers_vibrate) {
-        return;
+bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+                                               const VibrationValue& vibration_value) {
+    if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) {
+        return false;
     }
-    bool success = true;
-    for (std::size_t i = 0; i < controllers.size(); ++i) {
-        if (!connected_controllers[i].is_connected) {
-            continue;
+
+    const auto& player = Settings::values.players.GetValue()[npad_index];
+
+    if (!player.vibration_enabled) {
+        if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f ||
+            latest_vibration_values[npad_index][device_index].amp_high != 0.0f) {
+            // Send an empty vibration to stop any vibrations.
+            vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f);
+            // Then reset the vibration value to its default value.
+            latest_vibration_values[npad_index][device_index] = {};
         }
-        using namespace Settings::NativeButton;
-        const auto& button_state = buttons[i];
-        if (button_state[A - BUTTON_HID_BEGIN]) {
-            if (button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay(
-                    vibrations[0].amp_high, vibrations[0].amp_low, vibrations[0].freq_high,
-                    vibrations[0].freq_low)) {
-                success = false;
-            }
+
+        return false;
+    }
+
+    if (!Settings::values.enable_accurate_vibrations.GetValue()) {
+        using std::chrono::duration_cast;
+        using std::chrono::milliseconds;
+        using std::chrono::steady_clock;
+
+        const auto now = steady_clock::now();
+
+        // Filter out non-zero vibrations that are within 10ms of each other.
+        if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) &&
+            duration_cast<milliseconds>(now - last_vibration_timepoints[npad_index][device_index]) <
+                milliseconds(10)) {
+            return false;
         }
+
+        last_vibration_timepoints[npad_index][device_index] = now;
+    }
+
+    auto& vibration = vibrations[npad_index][device_index];
+    const auto player_vibration_strength = static_cast<f32>(player.vibration_strength);
+    const auto amp_low =
+        std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f);
+    const auto amp_high =
+        std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f);
+    return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high,
+                                    vibration_value.freq_high);
+}
+
+void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
+                                        const VibrationValue& vibration_value) {
+    if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
+        return;
+    }
+
+    const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+    const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+
+    if (!vibration_devices_mounted[npad_index][device_index] ||
+        !connected_controllers[npad_index].is_connected) {
+        return;
     }
-    if (success) {
-        last_processed_vibration = vibrations.back();
+
+    if (vibration_device_handle.device_index == DeviceIndex::None) {
+        UNREACHABLE_MSG("DeviceIndex should never be None!");
+        return;
+    }
+
+    // Some games try to send mismatched parameters in the device handle, block these.
+    if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft &&
+         (vibration_device_handle.npad_type == NpadType::JoyconRight ||
+          vibration_device_handle.device_index == DeviceIndex::Right)) ||
+        (connected_controllers[npad_index].type == NPadControllerType::JoyRight &&
+         (vibration_device_handle.npad_type == NpadType::JoyconLeft ||
+          vibration_device_handle.device_index == DeviceIndex::Left))) {
+        return;
+    }
+
+    // Filter out vibrations with equivalent values to reduce unnecessary state changes.
+    if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low &&
+        vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) {
+        return;
+    }
+
+    if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) {
+        latest_vibration_values[npad_index][device_index] = vibration_value;
     }
 }
 
-Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
-    return last_processed_vibration;
+void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+                                         const std::vector<VibrationValue>& vibration_values) {
+    if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
+        return;
+    }
+
+    ASSERT_OR_EXECUTE_MSG(
+        vibration_device_handles.size() == vibration_values.size(), { return; },
+        "The amount of device handles does not match with the amount of vibration values,"
+        "this is undefined behavior!");
+
+    for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) {
+        VibrateController(vibration_device_handles[i], vibration_values[i]);
+    }
+}
+
+Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
+    const DeviceHandle& vibration_device_handle) const {
+    const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+    const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+    return latest_vibration_values[npad_index][device_index];
+}
+
+void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
+    const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+    const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+    InitializeVibrationDeviceAtIndex(npad_index, device_index);
+}
+
+void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
+                                                       std::size_t device_index) {
+    if (vibrations[npad_index][device_index]) {
+        vibration_devices_mounted[npad_index][device_index] =
+            vibrations[npad_index][device_index]->GetStatus() == 1;
+    } else {
+        vibration_devices_mounted[npad_index][device_index] = false;
+    }
+}
+
+void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
+    permit_vibration_session_enabled = permit_vibration_session;
+}
+
+bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
+    const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
+    const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
+    return vibration_devices_mounted[npad_index][device_index];
 }
 
 std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
@@ -696,31 +837,38 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz
 void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
                                          bool connected) {
     if (!connected) {
-        DisconnectNPadAtIndex(npad_index);
+        DisconnectNpadAtIndex(npad_index);
         return;
     }
 
     if (controller == NPadControllerType::Handheld) {
-        Settings::values.players[HANDHELD_INDEX].controller_type =
+        Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type =
             MapNPadToSettingsType(controller);
-        Settings::values.players[HANDHELD_INDEX].connected = true;
+        Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
         connected_controllers[HANDHELD_INDEX] = {controller, true};
         InitNewlyAddedController(HANDHELD_INDEX);
         return;
     }
 
-    Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller);
-    Settings::values.players[npad_index].connected = true;
+    Settings::values.players.GetValue()[npad_index].controller_type =
+        MapNPadToSettingsType(controller);
+    Settings::values.players.GetValue()[npad_index].connected = true;
     connected_controllers[npad_index] = {controller, true};
     InitNewlyAddedController(npad_index);
 }
 
-void Controller_NPad::DisconnectNPad(u32 npad_id) {
-    DisconnectNPadAtIndex(NPadIdToIndex(npad_id));
+void Controller_NPad::DisconnectNpad(u32 npad_id) {
+    DisconnectNpadAtIndex(NPadIdToIndex(npad_id));
 }
 
-void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) {
-    Settings::values.players[npad_index].connected = false;
+void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
+    for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) {
+        // Send an empty vibration to stop any vibrations.
+        VibrateControllerAtIndex(npad_index, device_idx, {});
+        vibration_devices_mounted[npad_index][device_idx] = false;
+    }
+
+    Settings::values.players.GetValue()[npad_index].connected = false;
     connected_controllers[npad_index].is_connected = false;
 
     auto& controller = shared_memory_entries[npad_index];
@@ -758,7 +906,7 @@ void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
         (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
          connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
         // Disconnect the joycon at the second id and connect the dual joycon at the first index.
-        DisconnectNPad(npad_id_2);
+        DisconnectNpad(npad_id_2);
         AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
     }
 }
@@ -830,14 +978,6 @@ void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_prot
     unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled;
 }
 
-void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
-    can_controllers_vibrate = can_vibrate;
-}
-
-bool Controller_NPad::IsVibrationEnabled() const {
-    return can_controllers_vibrate;
-}
-
 void Controller_NPad::ClearAllConnectedControllers() {
     for (auto& controller : connected_controllers) {
         if (controller.is_connected && controller.type != NPadControllerType::None) {
@@ -882,7 +1022,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
             return false;
         }
         // Handheld should not be supported in docked mode
-        if (Settings::values.use_docked_mode) {
+        if (Settings::values.use_docked_mode.GetValue()) {
             return false;
         }
 

+ 86 - 45
src/core/hle/service/hid/controllers/npad.h

@@ -39,28 +39,30 @@ public:
     // Called when input devices should be loaded
     void OnLoadInputDevices() override;
 
-    struct NPadType {
-        union {
-            u32_le raw{};
-
-            BitField<0, 1, u32> pro_controller;
-            BitField<1, 1, u32> handheld;
-            BitField<2, 1, u32> joycon_dual;
-            BitField<3, 1, u32> joycon_left;
-            BitField<4, 1, u32> joycon_right;
+    enum class NPadControllerType {
+        None,
+        ProController,
+        Handheld,
+        JoyDual,
+        JoyLeft,
+        JoyRight,
+        Pokeball,
+    };
 
-            BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
-        };
+    enum class NpadType : u8 {
+        ProController = 3,
+        Handheld = 4,
+        JoyconDual = 5,
+        JoyconLeft = 6,
+        JoyconRight = 7,
+        Pokeball = 9,
     };
-    static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
 
-    struct Vibration {
-        f32 amp_low;
-        f32 freq_low;
-        f32 amp_high;
-        f32 freq_high;
+    enum class DeviceIndex : u8 {
+        Left = 0,
+        Right = 1,
+        None = 2,
     };
-    static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
 
     enum class GyroscopeZeroDriftMode : u32 {
         Loose = 0,
@@ -73,7 +75,7 @@ public:
         Horizontal = 1,
     };
 
-    enum class NPadAssignments : u32_le {
+    enum class NpadAssignments : u32 {
         Dual = 0,
         Single = 1,
     };
@@ -84,15 +86,36 @@ public:
         None = 2,
     };
 
-    enum class NPadControllerType {
-        None,
-        ProController,
-        Handheld,
-        JoyDual,
-        JoyLeft,
-        JoyRight,
-        Pokeball,
+    struct DeviceHandle {
+        NpadType npad_type{};
+        u8 npad_id{};
+        DeviceIndex device_index{};
+        INSERT_PADDING_BYTES(1);
+    };
+    static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
+
+    struct NpadStyleSet {
+        union {
+            u32_le raw{};
+
+            BitField<0, 1, u32> pro_controller;
+            BitField<1, 1, u32> handheld;
+            BitField<2, 1, u32> joycon_dual;
+            BitField<3, 1, u32> joycon_left;
+            BitField<4, 1, u32> joycon_right;
+
+            BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
+        };
+    };
+    static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
+
+    struct VibrationValue {
+        f32 amp_low{0.0f};
+        f32 freq_low{160.0f};
+        f32 amp_high{0.0f};
+        f32 freq_high{320.0f};
     };
+    static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size");
 
     struct LedPattern {
         explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
@@ -110,12 +133,12 @@ public:
         };
     };
 
-    void SetSupportedStyleSet(NPadType style_set);
-    NPadType GetSupportedStyleSet() const;
+    void SetSupportedStyleSet(NpadStyleSet style_set);
+    NpadStyleSet GetSupportedStyleSet() const;
 
-    void SetSupportedNPadIdTypes(u8* data, std::size_t length);
+    void SetSupportedNpadIdTypes(u8* data, std::size_t length);
     void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
-    std::size_t GetSupportedNPadIdTypesSize() const;
+    std::size_t GetSupportedNpadIdTypesSize() const;
 
     void SetHoldType(NpadHoldType joy_hold_type);
     NpadHoldType GetHoldType() const;
@@ -123,12 +146,26 @@ public:
     void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
     NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
 
-    void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
+    void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
+
+    bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
+                                  const VibrationValue& vibration_value);
+
+    void VibrateController(const DeviceHandle& vibration_device_handle,
+                           const VibrationValue& vibration_value);
+
+    void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
+                            const std::vector<VibrationValue>& vibration_values);
+
+    VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const;
+
+    void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle);
+
+    void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index);
 
-    void VibrateController(const std::vector<u32>& controllers,
-                           const std::vector<Vibration>& vibrations);
+    void SetPermitVibrationSession(bool permit_vibration_session);
 
-    Vibration GetLastVibration() const;
+    bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const;
 
     std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
     void SignalStyleSetChangedEvent(u32 npad_id) const;
@@ -138,8 +175,8 @@ public:
     // Adds a new controller at an index with connection status.
     void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
 
-    void DisconnectNPad(u32 npad_id);
-    void DisconnectNPadAtIndex(std::size_t index);
+    void DisconnectNpad(u32 npad_id);
+    void DisconnectNpadAtIndex(std::size_t index);
 
     void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
     GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
@@ -148,8 +185,6 @@ public:
     LedPattern GetLedPattern(u32 npad_id);
     bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
     void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
-    void SetVibrationEnabled(bool can_vibrate);
-    bool IsVibrationEnabled() const;
     void ClearAllConnectedControllers();
     void DisconnectAllConnectedControllers();
     void ConnectAllDisconnectedControllers();
@@ -324,8 +359,8 @@ private:
     };
 
     struct NPadEntry {
-        NPadType joy_styles;
-        NPadAssignments pad_assignment;
+        NpadStyleSet joy_styles;
+        NpadAssignments pad_assignment;
 
         ColorReadError single_color_error;
         ControllerColor single_color;
@@ -368,7 +403,7 @@ private:
 
     u32 press_state{};
 
-    NPadType style{};
+    NpadStyleSet style{};
     std::array<NPadEntry, 10> shared_memory_entries{};
     using ButtonArray = std::array<
         std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
@@ -376,22 +411,28 @@ private:
     using StickArray = std::array<
         std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
         10>;
+    using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
+                                                 Settings::NativeVibration::NUM_VIBRATIONS_HID>,
+                                      10>;
     using MotionArray = std::array<
-        std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>,
+        std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
         10>;
     ButtonArray buttons;
     StickArray sticks;
+    VibrationArray vibrations;
     MotionArray motions;
     std::vector<u32> supported_npad_id_types{};
     NpadHoldType hold_type{NpadHoldType::Vertical};
     NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
     // Each controller should have their own styleset changed event
     std::array<Kernel::EventPair, 10> styleset_changed_events;
-    Vibration last_processed_vibration{};
+    std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10> last_vibration_timepoints;
+    std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
+    bool permit_vibration_session_enabled{false};
+    std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
     std::array<ControllerHolder, 10> connected_controllers{};
     std::array<bool, 10> unintended_home_button_input_protection{};
     GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
-    bool can_controllers_vibrate{true};
     bool sixaxis_sensors_enabled{true};
     bool sixaxis_at_rest{true};
     std::array<ControllerPad, 10> npad_pad_states{};

Разница между файлами не показана из-за своего большого размера
+ 440 - 231
src/core/hle/service/hid/hid.cpp


+ 27 - 10
src/core/hle/service/hid/hid.h

@@ -86,17 +86,15 @@ public:
 
 private:
     void CreateAppletResource(Kernel::HLERequestContext& ctx);
-    void ActivateXpad(Kernel::HLERequestContext& ctx);
-    void GetXpadIDs(Kernel::HLERequestContext& ctx);
-    void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
-    void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
     void ActivateDebugPad(Kernel::HLERequestContext& ctx);
     void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
     void ActivateMouse(Kernel::HLERequestContext& ctx);
     void ActivateKeyboard(Kernel::HLERequestContext& ctx);
     void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx);
-    void ActivateGesture(Kernel::HLERequestContext& ctx);
-    void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
+    void ActivateXpad(Kernel::HLERequestContext& ctx);
+    void GetXpadIDs(Kernel::HLERequestContext& ctx);
+    void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
+    void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
     void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
     void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
     void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx);
@@ -104,6 +102,7 @@ private:
     void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
     void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
     void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
+    void ActivateGesture(Kernel::HLERequestContext& ctx);
     void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
     void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
     void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
@@ -112,6 +111,7 @@ private:
     void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
     void DisconnectNpad(Kernel::HLERequestContext& ctx);
     void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
+    void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
     void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
     void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
     void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
@@ -125,15 +125,16 @@ private:
     void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
     void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx);
     void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx);
-    void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
-    void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+    void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
     void SendVibrationValue(Kernel::HLERequestContext& ctx);
-    void SendVibrationValues(Kernel::HLERequestContext& ctx);
     void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
-    void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
     void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
     void PermitVibration(Kernel::HLERequestContext& ctx);
     void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
+    void SendVibrationValues(Kernel::HLERequestContext& ctx);
+    void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
+    void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
+    void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx);
     void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
     void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
     void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
@@ -146,6 +147,22 @@ private:
     void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
     void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
 
+    enum class VibrationDeviceType : u32 {
+        LinearResonantActuator = 1,
+    };
+
+    enum class VibrationDevicePosition : u32 {
+        None = 0,
+        Left = 1,
+        Right = 2,
+    };
+
+    struct VibrationDeviceInfo {
+        VibrationDeviceType type{};
+        VibrationDevicePosition position{};
+    };
+    static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
+
     std::shared_ptr<IAppletResource> applet_resource;
     Core::System& system;
 };

+ 1 - 1
src/core/hle/service/vi/vi.cpp

@@ -771,7 +771,7 @@ private:
         IPC::ResponseBuilder rb{ctx, 6};
         rb.Push(RESULT_SUCCESS);
 
-        if (Settings::values.use_docked_mode) {
+        if (Settings::values.use_docked_mode.GetValue()) {
             rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
                     static_cast<u32>(Settings::values.resolution_factor.GetValue()));
             rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *

+ 7 - 1
src/core/settings.cpp

@@ -49,7 +49,7 @@ void LogSettings() {
     };
 
     LOG_INFO(Config, "yuzu Configuration:");
-    log_setting("Controls_UseDockedMode", values.use_docked_mode);
+    log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
     log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
     log_setting("System_CurrentUser", values.current_user);
     log_setting("System_LanguageIndex", values.language_index.GetValue());
@@ -145,6 +145,12 @@ void RestoreGlobalState() {
     values.rng_seed.SetGlobal(true);
     values.custom_rtc.SetGlobal(true);
     values.sound_index.SetGlobal(true);
+
+    // Controls
+    values.players.SetGlobal(true);
+    values.use_docked_mode.SetGlobal(true);
+    values.vibration_enabled.SetGlobal(true);
+    values.motion_enabled.SetGlobal(true);
 }
 
 void Sanitize() {

+ 47 - 11
src/core/settings.h

@@ -65,6 +65,38 @@ private:
     Type local{};
 };
 
+/**
+ * The InputSetting class allows for getting a reference to either the global or local members.
+ * This is required as we cannot easily modify the values of user-defined types within containers
+ * using the SetValue() member function found in the Setting class. The primary purpose of this
+ * class is to store an array of 10 PlayerInput structs for both the global and local (per-game)
+ * setting and allows for easily accessing and modifying both settings.
+ */
+template <typename Type>
+class InputSetting final {
+public:
+    InputSetting() = default;
+    explicit InputSetting(Type val) : global{val} {}
+    ~InputSetting() = default;
+    void SetGlobal(bool to_global) {
+        use_global = to_global;
+    }
+    bool UsingGlobal() const {
+        return use_global;
+    }
+    Type& GetValue(bool need_global = false) {
+        if (use_global || need_global) {
+            return global;
+        }
+        return local;
+    }
+
+private:
+    bool use_global = true;
+    Type global{};
+    Type local{};
+};
+
 struct TouchFromButtonMap {
     std::string name;
     std::vector<std::string> buttons;
@@ -133,9 +165,18 @@ struct Values {
     Setting<s32> sound_index;
 
     // Controls
-    std::array<PlayerInput, 10> players;
+    InputSetting<std::array<PlayerInput, 10>> players;
+
+    Setting<bool> use_docked_mode;
 
-    bool use_docked_mode;
+    Setting<bool> vibration_enabled;
+    Setting<bool> enable_accurate_vibrations;
+
+    Setting<bool> motion_enabled;
+    std::string motion_device;
+    std::string udp_input_address;
+    u16 udp_input_port;
+    u8 udp_pad_index;
 
     bool mouse_enabled;
     std::string mouse_device;
@@ -149,20 +190,15 @@ struct Values {
     ButtonsRaw debug_pad_buttons;
     AnalogsRaw debug_pad_analogs;
 
-    bool vibration_enabled;
-
-    bool motion_enabled;
-    std::string motion_device;
-    std::string touch_device;
     TouchscreenInput touchscreen;
-    std::atomic_bool is_device_reload_pending{true};
+
     bool use_touch_from_button;
+    std::string touch_device;
     int touch_from_button_map_index;
-    std::string udp_input_address;
-    u16 udp_input_port;
-    u8 udp_pad_index;
     std::vector<TouchFromButtonMap> touch_from_button_maps;
 
+    std::atomic_bool is_device_reload_pending{true};
+
     // Data Storage
     bool use_virtual_sd;
     bool gamecard_inserted;

+ 1 - 1
src/core/telemetry_session.cpp

@@ -213,7 +213,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
              Settings::values.use_assembly_shaders.GetValue());
     AddField(field_type, "Renderer_UseAsynchronousShaders",
              Settings::values.use_asynchronous_shaders.GetValue());
-    AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
+    AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
 }
 
 bool TelemetrySession::SubmitTestcase() {

+ 2 - 4
src/input_common/gcadapter/gc_adapter.cpp

@@ -230,10 +230,8 @@ void Adapter::SendVibrations() {
     vibration_changed = false;
 }
 
-bool Adapter::RumblePlay(std::size_t port, f32 amplitude) {
-    amplitude = std::clamp(amplitude, 0.0f, 1.0f);
-    const auto raw_amp = static_cast<u8>(amplitude * 0x8);
-    pads[port].rumble_amplitude = raw_amp;
+bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
+    pads[port].rumble_amplitude = amplitude;
 
     return rumble_enabled;
 }

+ 2 - 2
src/input_common/gcadapter/gc_adapter.h

@@ -77,8 +77,8 @@ public:
     Adapter();
     ~Adapter();
 
-    /// Request a vibration for a controlelr
-    bool RumblePlay(std::size_t port, f32 amplitude);
+    /// Request a vibration for a controller
+    bool RumblePlay(std::size_t port, u8 amplitude);
 
     /// Used for polling
     void BeginConfiguration();

+ 40 - 10
src/input_common/gcadapter/gc_poller.cpp

@@ -15,7 +15,7 @@ namespace InputCommon {
 
 class GCButton final : public Input::ButtonDevice {
 public:
-    explicit GCButton(u32 port_, s32 button_, GCAdapter::Adapter* adapter)
+    explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
         : port(port_), button(button_), gcadapter(adapter) {}
 
     ~GCButton() override;
@@ -27,18 +27,10 @@ public:
         return false;
     }
 
-    bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
-        const float amplitude = amp_high + amp_low > 2.0f ? 1.0f : (amp_high + amp_low) * 0.5f;
-        const auto new_amp =
-            static_cast<f32>(pow(amplitude, 0.5f) * (3.0f - 2.0f * pow(amplitude, 0.15f)));
-
-        return gcadapter->RumblePlay(port, new_amp);
-    }
-
 private:
     const u32 port;
     const s32 button;
-    GCAdapter::Adapter* gcadapter;
+    const GCAdapter::Adapter* gcadapter;
 };
 
 class GCAxisButton final : public Input::ButtonDevice {
@@ -299,4 +291,42 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
     return params;
 }
 
+class GCVibration final : public Input::VibrationDevice {
+public:
+    explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
+        : port(port_), gcadapter(adapter) {}
+
+    u8 GetStatus() const override {
+        return gcadapter->RumblePlay(port, 0);
+    }
+
+    bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override {
+        const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
+        const auto processed_amplitude = static_cast<u8>(
+            pow(mean_amplitude, 0.5f) * (3.0f - 2.0f * pow(mean_amplitude, 0.15f)) * 0x8);
+
+        return gcadapter->RumblePlay(port, processed_amplitude);
+    }
+
+private:
+    const u32 port;
+    GCAdapter::Adapter* gcadapter;
+};
+
+/// An vibration device factory that creates vibration devices from GC Adapter
+GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
+    : adapter(std::move(adapter_)) {}
+
+/**
+ * Creates a vibration device from a joystick
+ * @param params contains parameters for creating the device:
+ *     - "port": the nth gcpad on the adapter
+ */
+std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
+    const Common::ParamPackage& params) {
+    const auto port = static_cast<u32>(params.Get("port", 0));
+
+    return std::make_unique<GCVibration>(port, adapter.get());
+}
+
 } // namespace InputCommon

+ 11 - 0
src/input_common/gcadapter/gc_poller.h

@@ -64,4 +64,15 @@ private:
     bool polling = false;
 };
 
+/// A vibration device factory creates vibration devices from GC Adapter
+class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+    explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
+
+    std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
+
+private:
+    std::shared_ptr<GCAdapter::Adapter> adapter;
+};
+
 } // namespace InputCommon

+ 6 - 9
src/input_common/main.cpp

@@ -28,6 +28,8 @@ struct InputSubsystem::Impl {
         Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
         gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
         Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
+        gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
+        Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
 
         keyboard = std::make_shared<Keyboard>();
         Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
@@ -64,9 +66,11 @@ struct InputSubsystem::Impl {
 #endif
         Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
         Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
+        Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
 
         gcbuttons.reset();
         gcanalog.reset();
+        gcvibration.reset();
 
         Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
         Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
@@ -78,7 +82,7 @@ struct InputSubsystem::Impl {
     [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
         std::vector<Common::ParamPackage> devices = {
             Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
-            Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
+            Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
         };
 #ifdef HAVE_SDL2
         auto sdl_devices = sdl->GetInputDevices();
@@ -96,10 +100,6 @@ struct InputSubsystem::Impl {
         if (!params.Has("class") || params.Get("class", "") == "any") {
             return {};
         }
-        if (params.Get("class", "") == "key") {
-            // TODO consider returning the SDL key codes for the default keybindings
-            return {};
-        }
         if (params.Get("class", "") == "gcpad") {
             return gcadapter->GetAnalogMappingForDevice(params);
         }
@@ -116,10 +116,6 @@ struct InputSubsystem::Impl {
         if (!params.Has("class") || params.Get("class", "") == "any") {
             return {};
         }
-        if (params.Get("class", "") == "key") {
-            // TODO consider returning the SDL key codes for the default keybindings
-            return {};
-        }
         if (params.Get("class", "") == "gcpad") {
             return gcadapter->GetButtonMappingForDevice(params);
         }
@@ -150,6 +146,7 @@ struct InputSubsystem::Impl {
 #endif
     std::shared_ptr<GCButtonFactory> gcbuttons;
     std::shared_ptr<GCAnalogFactory> gcanalog;
+    std::shared_ptr<GCVibrationFactory> gcvibration;
     std::shared_ptr<UDPMotionFactory> udpmotion;
     std::shared_ptr<UDPTouchFactory> udptouch;
     std::shared_ptr<CemuhookUDP::Client> udp;

+ 71 - 67
src/input_common/sdl/sdl_impl.cpp

@@ -80,30 +80,13 @@ public:
         return static_cast<float>(state.axes.at(axis)) / (32767.0f * range);
     }
 
-    bool RumblePlay(f32 amp_low, f32 amp_high, u32 time) {
-        const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF);
-        const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF);
-        // Lower drastically the number of state changes
-        if (raw_amp_low >> 11 == last_state_rumble_low >> 11 &&
-            raw_amp_high >> 11 == last_state_rumble_high >> 11) {
-            if (raw_amp_low + raw_amp_high != 0 ||
-                last_state_rumble_low + last_state_rumble_high == 0) {
-                return false;
-            }
-        }
-        // Don't change state if last vibration was < 20ms
-        const auto now = std::chrono::system_clock::now();
-        if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) <
-            std::chrono::milliseconds(20)) {
-            return raw_amp_low + raw_amp_high == 0;
+    bool RumblePlay(u16 amp_low, u16 amp_high) {
+        if (sdl_controller) {
+            return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0;
+        } else if (sdl_joystick) {
+            return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0;
         }
 
-        last_vibration = now;
-        last_state_rumble_low = raw_amp_low;
-        last_state_rumble_high = raw_amp_high;
-        if (sdl_joystick) {
-            SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time);
-        }
         return false;
     }
 
@@ -172,9 +155,6 @@ private:
     } state;
     std::string guid;
     int port;
-    u16 last_state_rumble_high = 0;
-    u16 last_state_rumble_low = 0;
-    std::chrono::time_point<std::chrono::system_clock> last_vibration;
     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
     mutable std::mutex mutex;
@@ -327,12 +307,6 @@ public:
         return joystick->GetButton(button);
     }
 
-    bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
-        const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f));
-        const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f));
-        return joystick->RumblePlay(new_amp_low, new_amp_high, 250);
-    }
-
 private:
     std::shared_ptr<SDLJoystick> joystick;
     int button;
@@ -416,6 +390,32 @@ private:
     const float range;
 };
 
+class SDLVibration final : public Input::VibrationDevice {
+public:
+    explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
+        : joystick(std::move(joystick_)) {}
+
+    u8 GetStatus() const override {
+        joystick->RumblePlay(1, 1);
+        return joystick->RumblePlay(0, 0);
+    }
+
+    bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override {
+        const auto process_amplitude = [](f32 amplitude) {
+            return static_cast<u16>(std::pow(amplitude, 0.5f) *
+                                    (3.0f - 2.0f * std::pow(amplitude, 0.15f)) * 0xFFFF);
+        };
+
+        const auto processed_amp_low = process_amplitude(amp_low);
+        const auto processed_amp_high = process_amplitude(amp_high);
+
+        return joystick->RumblePlay(processed_amp_low, processed_amp_high);
+    }
+
+private:
+    std::shared_ptr<SDLJoystick> joystick;
+};
+
 class SDLDirectionMotion final : public Input::MotionDevice {
 public:
     explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
@@ -558,7 +558,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
 public:
     explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
     /**
-     * Creates analog device from joystick axes
+     * Creates an analog device from joystick axes
      * @param params contains parameters for creating the device:
      *     - "guid": the guid of the joystick to bind
      *     - "port": the nth joystick of the same type
@@ -584,6 +584,26 @@ private:
     SDLState& state;
 };
 
+/// An vibration device factory that creates vibration devices from SDL joystick
+class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
+public:
+    explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
+    /**
+     * Creates a vibration device from a joystick
+     * @param params contains parameters for creating the device:
+     *     - "guid": the guid of the joystick to bind
+     *     - "port": the nth joystick of the same type
+     */
+    std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
+        const std::string guid = params.Get("guid", "0");
+        const int port = params.Get("port", 0);
+        return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
+    }
+
+private:
+    SDLState& state;
+};
+
 /// A motion device factory that creates motion devices from SDL joystick
 class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
 public:
@@ -650,11 +670,13 @@ private:
 
 SDLState::SDLState() {
     using namespace Input;
-    analog_factory = std::make_shared<SDLAnalogFactory>(*this);
     button_factory = std::make_shared<SDLButtonFactory>(*this);
+    analog_factory = std::make_shared<SDLAnalogFactory>(*this);
+    vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
     motion_factory = std::make_shared<SDLMotionFactory>(*this);
-    RegisterFactory<AnalogDevice>("sdl", analog_factory);
     RegisterFactory<ButtonDevice>("sdl", button_factory);
+    RegisterFactory<AnalogDevice>("sdl", analog_factory);
+    RegisterFactory<VibrationDevice>("sdl", vibration_factory);
     RegisterFactory<MotionDevice>("sdl", motion_factory);
 
     // If the frontend is going to manage the event loop, then we don't start one here
@@ -676,7 +698,7 @@ SDLState::SDLState() {
             using namespace std::chrono_literals;
             while (initialized) {
                 SDL_PumpEvents();
-                std::this_thread::sleep_for(5ms);
+                std::this_thread::sleep_for(1ms);
             }
         });
     }
@@ -691,6 +713,7 @@ SDLState::~SDLState() {
     using namespace Input;
     UnregisterFactory<ButtonDevice>("sdl");
     UnregisterFactory<AnalogDevice>("sdl");
+    UnregisterFactory<VibrationDevice>("sdl");
     UnregisterFactory<MotionDevice>("sdl");
 
     CloseJoysticks();
@@ -1045,7 +1068,6 @@ public:
 
     void Start(const std::string& device_id) override {
         SDLPoller::Start(device_id);
-        // Load the game controller
         // Reset stored axes
         analog_x_axis = -1;
         analog_y_axis = -1;
@@ -1058,40 +1080,21 @@ public:
             if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
                 continue;
             }
-            // Simplify controller config by testing if game controller support is enabled.
             if (event.type == SDL_JOYAXISMOTION) {
                 const auto axis = event.jaxis.axis;
-                if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
-                    auto* const controller = joystick->GetSDLGameController()) {
-                    const auto axis_left_x =
-                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
-                            .value.axis;
-                    const auto axis_left_y =
-                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
-                            .value.axis;
-                    const auto axis_right_x =
-                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
-                            .value.axis;
-                    const auto axis_right_y =
-                        SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
-                            .value.axis;
-
-                    if (axis == axis_left_x || axis == axis_left_y) {
-                        analog_x_axis = axis_left_x;
-                        analog_y_axis = axis_left_y;
-                        break;
-                    } else if (axis == axis_right_x || axis == axis_right_y) {
-                        analog_x_axis = axis_right_x;
-                        analog_y_axis = axis_right_y;
-                        break;
-                    }
+                // In order to return a complete analog param, we need inputs for both axes.
+                // First we take the x-axis (horizontal) input, then the y-axis (vertical) input.
+                if (analog_x_axis == -1) {
+                    analog_x_axis = axis;
+                } else if (analog_y_axis == -1 && analog_x_axis != axis) {
+                    analog_y_axis = axis;
+                }
+            } else {
+                // If the press wasn't accepted as a joy axis, check for a button press
+                auto button_press = button_poller.FromEvent(event);
+                if (button_press) {
+                    return *button_press;
                 }
-            }
-
-            // If the press wasn't accepted as a joy axis, check for a button press
-            auto button_press = button_poller.FromEvent(event);
-            if (button_press) {
-                return *button_press;
             }
         }
 
@@ -1104,6 +1107,7 @@ public:
                 return params;
             }
         }
+
         return {};
     }
 

+ 2 - 0
src/input_common/sdl/sdl_impl.h

@@ -22,6 +22,7 @@ namespace InputCommon::SDL {
 class SDLAnalogFactory;
 class SDLButtonFactory;
 class SDLMotionFactory;
+class SDLVibrationFactory;
 class SDLJoystick;
 
 class SDLState : public State {
@@ -72,6 +73,7 @@ private:
 
     std::shared_ptr<SDLButtonFactory> button_factory;
     std::shared_ptr<SDLAnalogFactory> analog_factory;
+    std::shared_ptr<SDLVibrationFactory> vibration_factory;
     std::shared_ptr<SDLMotionFactory> motion_factory;
 
     bool start_thread = false;

+ 14 - 7
src/input_common/settings.cpp

@@ -14,13 +14,6 @@ const std::array<const char*, NumButtons> mapping = {{
 }};
 }
 
-namespace NativeMotion {
-const std::array<const char*, NumMotions> mapping = {{
-    "motionleft",
-    "motionright",
-}};
-}
-
 namespace NativeAnalog {
 const std::array<const char*, NumAnalogs> mapping = {{
     "lstick",
@@ -28,6 +21,20 @@ const std::array<const char*, NumAnalogs> mapping = {{
 }};
 }
 
+namespace NativeVibration {
+const std::array<const char*, NumVibrations> mapping = {{
+    "left_vibration_device",
+    "right_vibration_device",
+}};
+}
+
+namespace NativeMotion {
+const std::array<const char*, NumMotions> mapping = {{
+    "motionleft",
+    "motionright",
+}};
+}
+
 namespace NativeMouseButton {
 const std::array<const char*, NumMouseButtons> mapping = {{
     "left",

+ 28 - 7
src/input_common/settings.h

@@ -66,17 +66,32 @@ constexpr int NUM_STICKS_HID = NumAnalogs;
 extern const std::array<const char*, NumAnalogs> mapping;
 } // namespace NativeAnalog
 
+namespace NativeVibration {
+enum Values : int {
+    LeftVibrationDevice,
+    RightVibrationDevice,
+
+    NumVibrations,
+};
+
+constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
+constexpr int VIBRATION_HID_END = NumVibrations;
+constexpr int NUM_VIBRATIONS_HID = NumVibrations;
+
+extern const std::array<const char*, NumVibrations> mapping;
+}; // namespace NativeVibration
+
 namespace NativeMotion {
 enum Values : int {
-    MOTIONLEFT,
-    MOTIONRIGHT,
+    MotionLeft,
+    MotionRight,
 
     NumMotions,
 };
 
-constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
+constexpr int MOTION_HID_BEGIN = MotionLeft;
 constexpr int MOTION_HID_END = NumMotions;
-constexpr int NUM_MOTION_HID = NumMotions;
+constexpr int NUM_MOTIONS_HID = NumMotions;
 
 extern const std::array<const char*, NumMotions> mapping;
 } // namespace NativeMotion
@@ -305,9 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
 
 } // namespace NativeKeyboard
 
-using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
 using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
-using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
+using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
+using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
+using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
+
 using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
 using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
 using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
@@ -330,7 +347,11 @@ struct PlayerInput {
     ControllerType controller_type;
     ButtonsRaw buttons;
     AnalogsRaw analogs;
-    MotionRaw motions;
+    VibrationsRaw vibrations;
+    MotionsRaw motions;
+
+    bool vibration_enabled;
+    int vibration_strength;
 
     u32 body_color_left;
     u32 body_color_right;

+ 1 - 1
src/input_common/udp/client.cpp

@@ -344,7 +344,7 @@ void TestCommunication(const std::string& host, u16 port, std::size_t pad_index,
         };
         Socket socket{host, port, pad_index, client_id, std::move(callback)};
         std::thread worker_thread{SocketLoop, &socket};
-        const bool result = success_event.WaitFor(std::chrono::seconds(8));
+        const bool result = success_event.WaitFor(std::chrono::seconds(5));
         socket.Stop();
         worker_thread.join();
         if (result) {

+ 8 - 3
src/yuzu/CMakeLists.txt

@@ -68,12 +68,12 @@ add_executable(yuzu
     configuration/configure_input_advanced.cpp
     configuration/configure_input_advanced.h
     configuration/configure_input_advanced.ui
-    configuration/configure_input_dialog.cpp
-    configuration/configure_input_dialog.h
-    configuration/configure_input_dialog.ui
     configuration/configure_input_player.cpp
     configuration/configure_input_player.h
     configuration/configure_input_player.ui
+    configuration/configure_input_profile_dialog.cpp
+    configuration/configure_input_profile_dialog.h
+    configuration/configure_input_profile_dialog.ui
     configuration/configure_motion_touch.cpp
     configuration/configure_motion_touch.h
     configuration/configure_motion_touch.ui
@@ -105,9 +105,14 @@ add_executable(yuzu
     configuration/configure_ui.cpp
     configuration/configure_ui.h
     configuration/configure_ui.ui
+    configuration/configure_vibration.cpp
+    configuration/configure_vibration.h
+    configuration/configure_vibration.ui
     configuration/configure_web.cpp
     configuration/configure_web.h
     configuration/configure_web.ui
+    configuration/input_profiles.cpp
+    configuration/input_profiles.h
     debugger/console.cpp
     debugger/console.h
     debugger/profiler.cpp

+ 0 - 20
src/yuzu/aboutdialog.ui

@@ -160,32 +160,12 @@ p, li { white-space: pre-wrap; }
    <signal>accepted()</signal>
    <receiver>AboutDialog</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>AboutDialog</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 95 - 59
src/yuzu/applets/controller.cpp

@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
+#include <thread>
 
 #include "common/assert.h"
 #include "common/string_util.h"
@@ -13,11 +14,16 @@
 #include "core/hle/service/sm/sm.h"
 #include "ui_controller.h"
 #include "yuzu/applets/controller.h"
-#include "yuzu/configuration/configure_input_dialog.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
 #include "yuzu/main.h"
 
 namespace {
 
+constexpr std::size_t HANDHELD_INDEX = 8;
+
 constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
     {true, false, false, false},
     {true, true, false, false},
@@ -106,7 +112,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
     QWidget* parent, Core::Frontend::ControllerParameters parameters_,
     InputCommon::InputSubsystem* input_subsystem_)
     : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
-      parameters(std::move(parameters_)), input_subsystem(input_subsystem_) {
+      parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
+      input_profiles(std::make_unique<InputProfiles>()) {
     ui->setupUi(this);
 
     player_widgets = {
@@ -223,12 +230,22 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
         }
     }
 
+    connect(ui->vibrationButton, &QPushButton::clicked, this,
+            &QtControllerSelectorDialog::CallConfigureVibrationDialog);
+
     connect(ui->inputConfigButton, &QPushButton::clicked, this,
-            &QtControllerSelectorDialog::CallConfigureInputDialog);
+            &QtControllerSelectorDialog::CallConfigureInputProfileDialog);
 
     connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
             &QtControllerSelectorDialog::ApplyConfiguration);
 
+    // Enhancement: Check if the parameters have already been met before disconnecting controllers.
+    // If all the parameters are met AND only allows a single player,
+    // stop the constructor here as we do not need to continue.
+    if (CheckIfParametersMet() && parameters.enable_single_mode) {
+        return;
+    }
+
     // If keep_controllers_connected is false, forcefully disconnect all controllers
     if (!parameters.keep_controllers_connected) {
         for (auto player : player_groupboxes) {
@@ -236,58 +253,66 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
         }
     }
 
-    CheckIfParametersMet();
-
     resize(0, 0);
 }
 
 QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
 
-void QtControllerSelectorDialog::ApplyConfiguration() {
-    // Update the controller state once more, just to be sure they are properly applied.
-    for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
-        UpdateControllerState(index);
+int QtControllerSelectorDialog::exec() {
+    if (parameters_met && parameters.enable_single_mode) {
+        return QDialog::Accepted;
     }
+    return QDialog::exec();
+}
 
-    const bool pre_docked_mode = Settings::values.use_docked_mode;
-    Settings::values.use_docked_mode = ui->radioDocked->isChecked();
-    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
+void QtControllerSelectorDialog::ApplyConfiguration() {
+    const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+    Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
 
-    Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
+    Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+    Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
 }
 
 void QtControllerSelectorDialog::LoadConfiguration() {
     for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
-        const auto connected = Settings::values.players[index].connected ||
-                               (index == 0 && Settings::values.players[8].connected);
+        const auto connected =
+            Settings::values.players.GetValue()[index].connected ||
+            (index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
         player_groupboxes[index]->setChecked(connected);
         connected_controller_checkboxes[index]->setChecked(connected);
         emulated_controllers[index]->setCurrentIndex(
-            GetIndexFromControllerType(Settings::values.players[index].controller_type));
+            GetIndexFromControllerType(Settings::values.players.GetValue()[index].controller_type));
     }
 
-    UpdateDockedState(Settings::values.players[8].connected);
+    UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
 
-    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
+    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+    ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
 }
 
-void QtControllerSelectorDialog::CallConfigureInputDialog() {
-    const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
-
-    ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
+void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
+    ConfigureVibration dialog(this);
 
     dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
                           Qt::WindowSystemMenuHint);
     dialog.setWindowModality(Qt::WindowModal);
-    dialog.exec();
 
-    dialog.ApplyConfiguration();
+    if (dialog.exec() == QDialog::Accepted) {
+        dialog.ApplyConfiguration();
+    }
+}
 
-    LoadConfiguration();
-    CheckIfParametersMet();
+void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
+    ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get());
+
+    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
+                          Qt::WindowSystemMenuHint);
+    dialog.setWindowModality(Qt::WindowModal);
+    dialog.exec();
 }
 
-void QtControllerSelectorDialog::CheckIfParametersMet() {
+bool QtControllerSelectorDialog::CheckIfParametersMet() {
     // Here, we check and validate the current configuration against all applicable parameters.
     const auto num_connected_players = static_cast<int>(
         std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
@@ -301,7 +326,7 @@ void QtControllerSelectorDialog::CheckIfParametersMet() {
         num_connected_players > max_supported_players) {
         parameters_met = false;
         ui->buttonBox->setEnabled(parameters_met);
-        return;
+        return parameters_met;
     }
 
     // Next, check against all connected controllers.
@@ -326,18 +351,13 @@ void QtControllerSelectorDialog::CheckIfParametersMet() {
         return true;
     }();
 
-    if (!all_controllers_compatible) {
-        parameters_met = false;
-        ui->buttonBox->setEnabled(parameters_met);
-        return;
-    }
-
-    parameters_met = true;
+    parameters_met = all_controllers_compatible;
     ui->buttonBox->setEnabled(parameters_met);
+    return parameters_met;
 }
 
 void QtControllerSelectorDialog::SetSupportedControllers() {
-    const QString theme = [this] {
+    const QString theme = [] {
         if (QIcon::themeName().contains(QStringLiteral("dark"))) {
             return QStringLiteral("_dark");
         } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@@ -426,7 +446,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
         }
     }();
 
-    const QString theme = [this] {
+    const QString theme = [] {
         if (QIcon::themeName().contains(QStringLiteral("dark"))) {
             return QStringLiteral("_dark");
         } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@@ -441,32 +461,48 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
 }
 
 void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
-    auto& player = Settings::values.players[player_index];
+    auto& player = Settings::values.players.GetValue()[player_index];
 
-    player.controller_type =
+    const auto controller_type =
         GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
-    player.connected = player_groupboxes[player_index]->isChecked();
+    const auto player_connected = player_groupboxes[player_index]->isChecked() &&
+                                  controller_type != Settings::ControllerType::Handheld;
 
-    // Player 2-8
-    if (player_index != 0) {
-        UpdateController(player.controller_type, player_index, player.connected);
+    if (player.controller_type == controller_type && player.connected == player_connected) {
+        // Set vibration devices in the event that the input device has changed.
+        ConfigureVibration::SetVibrationDevices(player_index);
         return;
     }
 
-    // Player 1 and Handheld
-    auto& handheld = Settings::values.players[8];
-    // If Handheld is selected, copy all the settings from Player 1 to Handheld.
-    if (player.controller_type == Settings::ControllerType::Handheld) {
-        handheld = player;
-        handheld.connected = player_groupboxes[player_index]->isChecked();
-        player.connected = false; // Disconnect Player 1
-    } else {
-        player.connected = player_groupboxes[player_index]->isChecked();
-        handheld.connected = false; // Disconnect Handheld
+    // Disconnect the controller first.
+    UpdateController(controller_type, player_index, false);
+
+    player.controller_type = controller_type;
+    player.connected = player_connected;
+
+    ConfigureVibration::SetVibrationDevices(player_index);
+
+    // Handheld
+    if (player_index == 0) {
+        auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+        if (controller_type == Settings::ControllerType::Handheld) {
+            handheld = player;
+        }
+        handheld.connected = player_groupboxes[player_index]->isChecked() &&
+                             controller_type == Settings::ControllerType::Handheld;
+        UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
     }
 
-    UpdateController(player.controller_type, player_index, player.connected);
-    UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
+    if (!player.connected) {
+        return;
+    }
+
+    // This emulates a delay between disconnecting and reconnecting controllers as some games
+    // do not respond to a change in controller type if it was instantaneous.
+    using namespace std::chrono_literals;
+    std::this_thread::sleep_for(20ms);
+
+    UpdateController(controller_type, player_index, player_connected);
 }
 
 void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
@@ -520,8 +556,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
     ui->radioDocked->setEnabled(!is_handheld);
     ui->radioUndocked->setEnabled(!is_handheld);
 
-    ui->radioDocked->setChecked(Settings::values.use_docked_mode);
-    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
+    ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
 
     // Also force into undocked mode if the controller type is handheld.
     if (is_handheld) {
@@ -564,8 +600,8 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
 
     for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
         // Disconnect any unsupported players here and disable or hide them if applicable.
-        Settings::values.players[index].connected = false;
-        UpdateController(Settings::values.players[index].controller_type, index, false);
+        Settings::values.players.GetValue()[index].connected = false;
+        UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false);
         // Hide the player widgets when max_supported_controllers is less than or equal to 4.
         if (max_supported_players <= 4) {
             player_widgets[index]->hide();

+ 14 - 5
src/yuzu/applets/controller.h

@@ -16,6 +16,8 @@ class QDialogButtonBox;
 class QGroupBox;
 class QLabel;
 
+class InputProfiles;
+
 namespace InputCommon {
 class InputSubsystem;
 }
@@ -33,6 +35,8 @@ public:
                                         InputCommon::InputSubsystem* input_subsystem_);
     ~QtControllerSelectorDialog() override;
 
+    int exec() override;
+
 private:
     // Applies the current configuration.
     void ApplyConfiguration();
@@ -40,12 +44,15 @@ private:
     // Loads the current input configuration into the frontend applet.
     void LoadConfiguration();
 
-    // Initializes the "Configure Input" Dialog.
-    void CallConfigureInputDialog();
+    // Initializes the "Configure Vibration" Dialog.
+    void CallConfigureVibrationDialog();
 
-    // Checks the current configuration against the given parameters and
-    // sets the value of parameters_met.
-    void CheckIfParametersMet();
+    // Initializes the "Create Input Profile" Dialog.
+    void CallConfigureInputProfileDialog();
+
+    // Checks the current configuration against the given parameters.
+    // This sets and returns the value of parameters_met.
+    bool CheckIfParametersMet();
 
     // Sets the controller icons for "Supported Controller Types".
     void SetSupportedControllers();
@@ -78,6 +85,8 @@ private:
 
     InputCommon::InputSubsystem* input_subsystem;
 
+    std::unique_ptr<InputProfiles> input_profiles;
+
     // This is true if and only if all parameters are met. Otherwise, this is false.
     // This determines whether the "OK" button can be clicked to exit the applet.
     bool parameters_met{false};

+ 15 - 34
src/yuzu/applets/controller.ui

@@ -1217,9 +1217,6 @@
               </item>
               <item>
                <widget class="QComboBox" name="comboPlayer3Emulated">
-                <property name="editable">
-                 <bool>false</bool>
-                </property>
                 <item>
                  <property name="text">
                   <string>Pro Controller</string>
@@ -2279,7 +2276,7 @@
              <number>6</number>
             </property>
             <property name="leftMargin">
-             <number>6</number>
+             <number>8</number>
             </property>
             <property name="topMargin">
              <number>6</number>
@@ -2332,30 +2329,24 @@
              <number>3</number>
             </property>
             <item>
-             <widget class="QSpinBox" name="vibrationSpin">
+             <widget class="QPushButton" name="vibrationButton">
               <property name="minimumSize">
                <size>
-                <width>65</width>
+                <width>68</width>
                 <height>0</height>
                </size>
               </property>
               <property name="maximumSize">
                <size>
-                <width>65</width>
+                <width>68</width>
                 <height>16777215</height>
                </size>
               </property>
-              <property name="suffix">
-               <string>%</string>
-              </property>
-              <property name="minimum">
-               <number>1</number>
-              </property>
-              <property name="maximum">
-               <number>200</number>
+              <property name="styleSheet">
+               <string notr="true">min-width: 68px;</string>
               </property>
-              <property name="value">
-               <number>100</number>
+              <property name="text">
+               <string>Configure</string>
               </property>
              </widget>
             </item>
@@ -2387,18 +2378,18 @@
              <widget class="QPushButton" name="motionButton">
               <property name="minimumSize">
                <size>
-                <width>57</width>
+                <width>68</width>
                 <height>0</height>
                </size>
               </property>
               <property name="maximumSize">
                <size>
-                <width>55</width>
+                <width>68</width>
                 <height>16777215</height>
                </size>
               </property>
               <property name="styleSheet">
-               <string notr="true">min-width: 55px;</string>
+               <string notr="true">min-width: 68px;</string>
               </property>
               <property name="text">
                <string>Configure</string>
@@ -2411,7 +2402,7 @@
          <item>
           <widget class="QGroupBox" name="inputConfigGroup">
            <property name="title">
-            <string>Input Config</string>
+            <string>Profiles</string>
            </property>
            <layout class="QHBoxLayout" name="horizontalLayout_7">
             <property name="leftMargin">
@@ -2430,15 +2421,15 @@
              <widget class="QPushButton" name="inputConfigButton">
               <property name="maximumSize">
                <size>
-                <width>65</width>
+                <width>68</width>
                 <height>16777215</height>
                </size>
               </property>
               <property name="styleSheet">
-               <string notr="true">min-width: 55px;</string>
+               <string notr="true">min-width: 68px;</string>
               </property>
               <property name="text">
-               <string>Open</string>
+               <string>Create</string>
               </property>
              </widget>
             </item>
@@ -2657,16 +2648,6 @@
    <signal>accepted()</signal>
    <receiver>QtControllerSelectorDialog</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>20</x>
-     <y>20</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>20</x>
-     <y>20</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 13 - 3
src/yuzu/bootmanager.cpp

@@ -382,7 +382,12 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
 }
 
 void GRenderWindow::mousePressEvent(QMouseEvent* event) {
-    // touch input is handled in TouchBeginEvent
+    if (!Settings::values.touchscreen.enabled) {
+        input_subsystem->GetKeyboard()->PressKey(event->button());
+        return;
+    }
+
+    // Touch input is handled in TouchBeginEvent
     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
         return;
     }
@@ -398,7 +403,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
 }
 
 void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
-    // touch input is handled in TouchUpdateEvent
+    // Touch input is handled in TouchUpdateEvent
     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
         return;
     }
@@ -411,7 +416,12 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
 }
 
 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
-    // touch input is handled in TouchEndEvent
+    if (!Settings::values.touchscreen.enabled) {
+        input_subsystem->GetKeyboard()->ReleaseKey(event->button());
+        return;
+    }
+
+    // Touch input is handled in TouchEndEvent
     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
         return;
     }

+ 218 - 114
src/yuzu/configuration/config.cpp

@@ -5,6 +5,7 @@
 #include <array>
 #include <QKeySequence>
 #include <QSettings>
+#include "common/common_paths.h"
 #include "common/file_util.h"
 #include "core/hle/service/acc/profile_manager.h"
 #include "core/hle/service/hid/controllers/npad.h"
@@ -14,14 +15,10 @@
 
 namespace FS = Common::FS;
 
-Config::Config(const std::string& config_file, bool is_global) {
-    // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
-    qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
-    FS::CreateFullPath(qt_config_loc);
-    qt_config =
-        std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
-    global = is_global;
-    Reload();
+Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
+    global = config_type == ConfigType::GlobalConfig;
+
+    Initialize(config_name);
 }
 
 Config::~Config() {
@@ -242,84 +239,152 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
 }};
 // clang-format on
 
-void Config::ReadPlayerValues() {
-    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
-        auto& player = Settings::values.players[p];
+void Config::Initialize(const std::string& config_name) {
+    switch (type) {
+    case ConfigType::GlobalConfig:
+        qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir),
+                                    config_name);
+        FS::CreateFullPath(qt_config_loc);
+        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+                                                QSettings::IniFormat);
+        Reload();
+        break;
+    case ConfigType::PerGameConfig:
+        qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini",
+                                    FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+        FS::CreateFullPath(qt_config_loc);
+        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+                                                QSettings::IniFormat);
+        Reload();
+        break;
+    case ConfigType::InputProfile:
+        qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini",
+                                    FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
+        FS::CreateFullPath(qt_config_loc);
+        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
+                                                QSettings::IniFormat);
+        break;
+    }
+}
+
+void Config::ReadPlayerValue(std::size_t player_index) {
+    const QString player_prefix = [this, player_index] {
+        if (type == ConfigType::InputProfile) {
+            return QString{};
+        } else {
+            return QStringLiteral("player_%1_").arg(player_index);
+        }
+    }();
+
+    auto& player = Settings::values.players.GetValue()[player_index];
+
+    if (player_prefix.isEmpty()) {
+        const auto controller = static_cast<Settings::ControllerType>(
+            qt_config
+                ->value(QStringLiteral("%1type").arg(player_prefix),
+                        static_cast<u8>(Settings::ControllerType::ProController))
+                .toUInt());
 
+        if (controller == Settings::ControllerType::LeftJoycon ||
+            controller == Settings::ControllerType::RightJoycon) {
+            player.controller_type = controller;
+        }
+    } else {
         player.connected =
-            ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
+            ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
+                .toBool();
 
         player.controller_type = static_cast<Settings::ControllerType>(
             qt_config
-                ->value(QStringLiteral("player_%1_type").arg(p),
+                ->value(QStringLiteral("%1type").arg(player_prefix),
                         static_cast<u8>(Settings::ControllerType::ProController))
                 .toUInt());
 
+        player.vibration_enabled =
+            qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
+                .toBool();
+
+        player.vibration_strength =
+            qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
+                .toInt();
+
         player.body_color_left = qt_config
-                                     ->value(QStringLiteral("player_%1_body_color_left").arg(p),
+                                     ->value(QStringLiteral("%1body_color_left").arg(player_prefix),
                                              Settings::JOYCON_BODY_NEON_BLUE)
                                      .toUInt();
-        player.body_color_right = qt_config
-                                      ->value(QStringLiteral("player_%1_body_color_right").arg(p),
-                                              Settings::JOYCON_BODY_NEON_RED)
-                                      .toUInt();
-        player.button_color_left = qt_config
-                                       ->value(QStringLiteral("player_%1_button_color_left").arg(p),
-                                               Settings::JOYCON_BUTTONS_NEON_BLUE)
-                                       .toUInt();
+        player.body_color_right =
+            qt_config
+                ->value(QStringLiteral("%1body_color_right").arg(player_prefix),
+                        Settings::JOYCON_BODY_NEON_RED)
+                .toUInt();
+        player.button_color_left =
+            qt_config
+                ->value(QStringLiteral("%1button_color_left").arg(player_prefix),
+                        Settings::JOYCON_BUTTONS_NEON_BLUE)
+                .toUInt();
         player.button_color_right =
             qt_config
-                ->value(QStringLiteral("player_%1_button_color_right").arg(p),
+                ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
                         Settings::JOYCON_BUTTONS_NEON_RED)
                 .toUInt();
+    }
 
-        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-            const std::string default_param =
-                InputCommon::GenerateKeyboardParam(default_buttons[i]);
-            auto& player_buttons = player.buttons[i];
-
-            player_buttons = qt_config
-                                 ->value(QStringLiteral("player_%1_").arg(p) +
-                                             QString::fromUtf8(Settings::NativeButton::mapping[i]),
-                                         QString::fromStdString(default_param))
-                                 .toString()
-                                 .toStdString();
-            if (player_buttons.empty()) {
-                player_buttons = default_param;
-            }
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        auto& player_buttons = player.buttons[i];
+
+        player_buttons = qt_config
+                             ->value(QStringLiteral("%1").arg(player_prefix) +
+                                         QString::fromUtf8(Settings::NativeButton::mapping[i]),
+                                     QString::fromStdString(default_param))
+                             .toString()
+                             .toStdString();
+        if (player_buttons.empty()) {
+            player_buttons = default_param;
         }
+    }
 
-        for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
-            const std::string default_param =
-                InputCommon::GenerateKeyboardParam(default_motions[i]);
-            auto& player_motions = player.motions[i];
-
-            player_motions = qt_config
-                                 ->value(QStringLiteral("player_%1_").arg(p) +
-                                             QString::fromUtf8(Settings::NativeMotion::mapping[i]),
-                                         QString::fromStdString(default_param))
-                                 .toString()
-                                 .toStdString();
-            if (player_motions.empty()) {
-                player_motions = default_param;
-            }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        auto& player_analogs = player.analogs[i];
+
+        player_analogs = qt_config
+                             ->value(QStringLiteral("%1").arg(player_prefix) +
+                                         QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
+                                     QString::fromStdString(default_param))
+                             .toString()
+                             .toStdString();
+        if (player_analogs.empty()) {
+            player_analogs = default_param;
         }
+    }
 
-        for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-            const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-                default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-                default_analogs[i][3], default_stick_mod[i], 0.5f);
-            auto& player_analogs = player.analogs[i];
-
-            player_analogs = qt_config
-                                 ->value(QStringLiteral("player_%1_").arg(p) +
-                                             QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
-                                         QString::fromStdString(default_param))
-                                 .toString()
-                                 .toStdString();
-            if (player_analogs.empty()) {
-                player_analogs = default_param;
-            }
+    for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+        auto& player_vibrations = player.vibrations[i];
+
+        player_vibrations =
+            qt_config
+                ->value(QStringLiteral("%1").arg(player_prefix) +
+                            QString::fromUtf8(Settings::NativeVibration::mapping[i]),
+                        QString{})
+                .toString()
+                .toStdString();
+    }
+
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        auto& player_motions = player.motions[i];
+
+        player_motions = qt_config
+                             ->value(QStringLiteral("%1").arg(player_prefix) +
+                                         QString::fromUtf8(Settings::NativeMotion::mapping[i]),
+                                     QString::fromStdString(default_param))
+                             .toString()
+                             .toStdString();
+        if (player_motions.empty()) {
+            player_motions = default_param;
         }
     }
 }
@@ -436,18 +501,21 @@ void Config::ReadAudioValues() {
 void Config::ReadControlValues() {
     qt_config->beginGroup(QStringLiteral("Controls"));
 
-    ReadPlayerValues();
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        ReadPlayerValue(p);
+    }
     ReadDebugValues();
     ReadKeyboardValues();
     ReadMouseValues();
     ReadTouchscreenValues();
     ReadMotionTouchValues();
 
-    Settings::values.vibration_enabled =
-        ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
-    Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool();
-    Settings::values.use_docked_mode =
-        ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
+    ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false);
+    ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"),
+                      true);
+    ReadSettingGlobal(Settings::values.enable_accurate_vibrations,
+                      QStringLiteral("enable_accurate_vibrations"), false);
+    ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true);
 
     qt_config->endGroup();
 }
@@ -920,49 +988,64 @@ void Config::ReadValues() {
     ReadSystemValues();
 }
 
-void Config::SavePlayerValues() {
-    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
-        const auto& player = Settings::values.players[p];
+void Config::SavePlayerValue(std::size_t player_index) {
+    const QString player_prefix = [this, player_index] {
+        if (type == ConfigType::InputProfile) {
+            return QString{};
+        } else {
+            return QStringLiteral("player_%1_").arg(player_index);
+        }
+    }();
 
-        WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
-        WriteSetting(QStringLiteral("player_%1_type").arg(p),
-                     static_cast<u8>(player.controller_type),
-                     static_cast<u8>(Settings::ControllerType::ProController));
+    const auto& player = Settings::values.players.GetValue()[player_index];
 
-        WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
+    WriteSetting(QStringLiteral("%1type").arg(player_prefix),
+                 static_cast<u8>(player.controller_type),
+                 static_cast<u8>(Settings::ControllerType::ProController));
+
+    if (!player_prefix.isEmpty()) {
+        WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false);
+        WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
+                     player.vibration_enabled, true);
+        WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
+                     player.vibration_strength, 100);
+        WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
                      Settings::JOYCON_BODY_NEON_BLUE);
-        WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
-                     Settings::JOYCON_BODY_NEON_RED);
-        WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
-                     Settings::JOYCON_BUTTONS_NEON_BLUE);
-        WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
+        WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
+                     player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
+        WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
+                     player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
+        WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
                      player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
+    }
 
-        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-            const std::string default_param =
-                InputCommon::GenerateKeyboardParam(default_buttons[i]);
-            WriteSetting(QStringLiteral("player_%1_").arg(p) +
-                             QString::fromStdString(Settings::NativeButton::mapping[i]),
-                         QString::fromStdString(player.buttons[i]),
-                         QString::fromStdString(default_param));
-        }
-        for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
-            const std::string default_param =
-                InputCommon::GenerateKeyboardParam(default_motions[i]);
-            WriteSetting(QStringLiteral("player_%1_").arg(p) +
-                             QString::fromStdString(Settings::NativeMotion::mapping[i]),
-                         QString::fromStdString(player.motions[i]),
-                         QString::fromStdString(default_param));
-        }
-        for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-            const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-                default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-                default_analogs[i][3], default_stick_mod[i], 0.5f);
-            WriteSetting(QStringLiteral("player_%1_").arg(p) +
-                             QString::fromStdString(Settings::NativeAnalog::mapping[i]),
-                         QString::fromStdString(player.analogs[i]),
-                         QString::fromStdString(default_param));
-        }
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+                         QString::fromStdString(Settings::NativeButton::mapping[i]),
+                     QString::fromStdString(player.buttons[i]),
+                     QString::fromStdString(default_param));
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+                         QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+                     QString::fromStdString(player.analogs[i]),
+                     QString::fromStdString(default_param));
+    }
+    for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
+        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+                         QString::fromStdString(Settings::NativeVibration::mapping[i]),
+                     QString::fromStdString(player.vibrations[i]), QString{});
+    }
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
+                         QString::fromStdString(Settings::NativeMotion::mapping[i]),
+                     QString::fromStdString(player.motions[i]),
+                     QString::fromStdString(default_param));
     }
 }
 
@@ -1087,14 +1170,20 @@ void Config::SaveAudioValues() {
 void Config::SaveControlValues() {
     qt_config->beginGroup(QStringLiteral("Controls"));
 
-    SavePlayerValues();
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        SavePlayerValue(p);
+    }
     SaveDebugValues();
     SaveMouseValues();
     SaveTouchscreenValues();
     SaveMotionTouchValues();
 
-    WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
-    WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
+    WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
+    WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled,
+                       true);
+    WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"),
+                       Settings::values.enable_accurate_vibrations, false);
+    WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
     WriteSetting(QStringLiteral("motion_device"),
                  QString::fromStdString(Settings::values.motion_device),
                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
@@ -1102,7 +1191,6 @@ void Config::SaveControlValues() {
                  QString::fromStdString(Settings::values.touch_device),
                  QStringLiteral("engine:emu_window"));
     WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
-    WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
 
     qt_config->endGroup();
 }
@@ -1515,3 +1603,19 @@ void Config::Save() {
     Settings::Sanitize();
     SaveValues();
 }
+
+void Config::ReadControlPlayerValue(std::size_t player_index) {
+    qt_config->beginGroup(QStringLiteral("Controls"));
+    ReadPlayerValue(player_index);
+    qt_config->endGroup();
+}
+
+void Config::SaveControlPlayerValue(std::size_t player_index) {
+    qt_config->beginGroup(QStringLiteral("Controls"));
+    SavePlayerValue(player_index);
+    qt_config->endGroup();
+}
+
+const std::string& Config::GetConfigFilePath() const {
+    return qt_config_loc;
+}

+ 18 - 4
src/yuzu/configuration/config.h

@@ -16,12 +16,24 @@ class QSettings;
 
 class Config {
 public:
-    explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true);
+    enum class ConfigType {
+        GlobalConfig,
+        PerGameConfig,
+        InputProfile,
+    };
+
+    explicit Config(const std::string& config_name = "qt-config",
+                    ConfigType config_type = ConfigType::GlobalConfig);
     ~Config();
 
     void Reload();
     void Save();
 
+    void ReadControlPlayerValue(std::size_t player_index);
+    void SaveControlPlayerValue(std::size_t player_index);
+
+    const std::string& GetConfigFilePath() const;
+
     static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
     static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
     static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
@@ -33,8 +45,10 @@ public:
     static const std::array<UISettings::Shortcut, 16> default_hotkeys;
 
 private:
+    void Initialize(const std::string& config_name);
+
     void ReadValues();
-    void ReadPlayerValues();
+    void ReadPlayerValue(std::size_t player_index);
     void ReadDebugValues();
     void ReadKeyboardValues();
     void ReadMouseValues();
@@ -62,7 +76,7 @@ private:
     void ReadWebServiceValues();
 
     void SaveValues();
-    void SavePlayerValues();
+    void SavePlayerValue(std::size_t player_index);
     void SaveDebugValues();
     void SaveMouseValues();
     void SaveTouchscreenValues();
@@ -111,9 +125,9 @@ private:
     void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
                             const QVariant& default_value);
 
+    ConfigType type;
     std::unique_ptr<QSettings> qt_config;
     std::string qt_config_loc;
-
     bool global;
 };
 

+ 0 - 20
src/yuzu/configuration/configure.ui

@@ -275,32 +275,12 @@
    <signal>accepted()</signal>
    <receiver>ConfigureDialog</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>220</x>
-     <y>380</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>220</x>
-     <y>200</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>ConfigureDialog</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>220</x>
-     <y>380</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>220</x>
-     <y>200</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 5 - 2
src/yuzu/configuration/configure_debug_controller.cpp

@@ -4,11 +4,14 @@
 
 #include "ui_configure_debug_controller.h"
 #include "yuzu/configuration/configure_debug_controller.h"
+#include "yuzu/configuration/configure_input_player.h"
 
 ConfigureDebugController::ConfigureDebugController(QWidget* parent,
-                                                   InputCommon::InputSubsystem* input_subsystem)
+                                                   InputCommon::InputSubsystem* input_subsystem,
+                                                   InputProfiles* profiles)
     : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
-      debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) {
+      debug_controller(
+          new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) {
     ui->setupUi(this);
 
     ui->controllerLayout->addWidget(debug_controller);

+ 6 - 3
src/yuzu/configuration/configure_debug_controller.h

@@ -6,10 +6,13 @@
 
 #include <memory>
 #include <QDialog>
-#include "yuzu/configuration/configure_input_player.h"
 
 class QPushButton;
 
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
 namespace InputCommon {
 class InputSubsystem;
 }
@@ -22,8 +25,8 @@ class ConfigureDebugController : public QDialog {
     Q_OBJECT
 
 public:
-    explicit ConfigureDebugController(QWidget* parent,
-                                      InputCommon::InputSubsystem* input_subsystem);
+    explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
+                                      InputProfiles* profiles);
     ~ConfigureDebugController() override;
 
     void ApplyConfiguration();

+ 0 - 20
src/yuzu/configuration/configure_debug_controller.ui

@@ -66,32 +66,12 @@
    <signal>accepted()</signal>
    <receiver>ConfigureDebugController</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>140</x>
-     <y>318</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>140</x>
-     <y>169</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>ConfigureDebugController</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>140</x>
-     <y>318</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>140</x>
-     <y>169</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 51 - 25
src/yuzu/configuration/configure_input.cpp

@@ -23,6 +23,8 @@
 #include "yuzu/configuration/configure_motion_touch.h"
 #include "yuzu/configuration/configure_mouse_advanced.h"
 #include "yuzu/configuration/configure_touchscreen_advanced.h"
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
 
 namespace {
 template <typename Dialog, typename... Args>
@@ -64,7 +66,8 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
 }
 
 ConfigureInput::ConfigureInput(QWidget* parent)
-    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
+      profiles(std::make_unique<InputProfiles>()) {
     ui->setupUi(this);
 }
 
@@ -73,14 +76,22 @@ ConfigureInput::~ConfigureInput() = default;
 void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
                                 std::size_t max_players) {
     player_controllers = {
-        new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem),
-        new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem),
+        new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
+        new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
+                                 profiles.get()),
     };
 
     player_tabs = {
@@ -113,8 +124,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
                 }
             }
         });
-        connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices,
-                [this] { UpdateAllInputDevices(); });
+        connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
+                &ConfigureInput::UpdateAllInputDevices);
+        connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
+                &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
         connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
             player_controllers[i]->ConnectPlayer(state == Qt::Checked);
         });
@@ -134,7 +147,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
     ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
     ui->tabAdvanced->layout()->addWidget(advanced);
     connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
-        CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem);
+        CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
     });
     connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
         CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
@@ -146,6 +159,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
                 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
             });
 
+    connect(ui->vibrationButton, &QPushButton::clicked,
+            [this] { CallConfigureDialog<ConfigureVibration>(*this); });
+
     connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
         CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
     });
@@ -171,12 +187,12 @@ void ConfigureInput::ApplyConfiguration() {
 
     advanced->ApplyConfiguration();
 
-    const bool pre_docked_mode = Settings::values.use_docked_mode;
-    Settings::values.use_docked_mode = ui->radioDocked->isChecked();
-    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
+    const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
+    Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
+    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
 
-    Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
-    Settings::values.motion_enabled = ui->motionGroup->isChecked();
+    Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
+    Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
 }
 
 void ConfigureInput::changeEvent(QEvent* event) {
@@ -193,16 +209,16 @@ void ConfigureInput::RetranslateUI() {
 
 void ConfigureInput::LoadConfiguration() {
     LoadPlayerControllerIndices();
-    UpdateDockedState(Settings::values.players[8].connected);
+    UpdateDockedState(Settings::values.players.GetValue()[8].connected);
 
-    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
-    ui->motionGroup->setChecked(Settings::values.motion_enabled);
+    ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
+    ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
 }
 
 void ConfigureInput::LoadPlayerControllerIndices() {
     for (std::size_t i = 0; i < player_connected.size(); ++i) {
-        const auto connected = Settings::values.players[i].connected ||
-                               (i == 0 && Settings::values.players[8].connected);
+        const auto connected = Settings::values.players.GetValue()[i].connected ||
+                               (i == 0 && Settings::values.players.GetValue()[8].connected);
         player_connected[i]->setChecked(connected);
     }
 }
@@ -231,8 +247,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
     ui->radioDocked->setEnabled(!is_handheld);
     ui->radioUndocked->setEnabled(!is_handheld);
 
-    ui->radioDocked->setChecked(Settings::values.use_docked_mode);
-    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
+    ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
+    ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
 
     // Also force into undocked mode if the controller type is handheld.
     if (is_handheld) {
@@ -242,6 +258,16 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
 
 void ConfigureInput::UpdateAllInputDevices() {
     for (const auto& player : player_controllers) {
-        player->UpdateInputDevices();
+        player->UpdateInputDeviceCombobox();
+    }
+}
+
+void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
+    for (std::size_t i = 0; i < player_controllers.size(); ++i) {
+        if (i == player_index) {
+            continue;
+        }
+
+        player_controllers[i]->UpdateInputProfiles();
     }
 }

+ 9 - 5
src/yuzu/configuration/configure_input.h

@@ -8,17 +8,18 @@
 #include <memory>
 
 #include <QKeyEvent>
+#include <QList>
 #include <QWidget>
 
-#include "yuzu/configuration/configure_input_advanced.h"
-#include "yuzu/configuration/configure_input_player.h"
-
-#include "ui_configure_input.h"
-
 class QCheckBox;
 class QString;
 class QTimer;
 
+class ConfigureInputAdvanced;
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
 namespace InputCommon {
 class InputSubsystem;
 }
@@ -51,6 +52,7 @@ private:
 
     void UpdateDockedState(bool is_handheld);
     void UpdateAllInputDevices();
+    void UpdateAllInputProfiles(std::size_t player_index);
 
     /// Load configuration settings.
     void LoadConfiguration();
@@ -61,6 +63,8 @@ private:
 
     std::unique_ptr<Ui::ConfigureInput> ui;
 
+    std::unique_ptr<InputProfiles> profiles;
+
     std::array<ConfigureInputPlayer*, 8> player_controllers;
     std::array<QWidget*, 8> player_tabs;
     std::array<QCheckBox*, 8> player_connected;

+ 20 - 26
src/yuzu/configuration/configure_input.ui

@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>700</width>
+    <width>680</width>
     <height>540</height>
    </rect>
   </property>
@@ -142,7 +142,7 @@
           <number>6</number>
          </property>
          <property name="leftMargin">
-          <number>3</number>
+          <number>8</number>
          </property>
          <property name="topMargin">
           <number>6</number>
@@ -195,30 +195,24 @@
           <number>3</number>
          </property>
          <item>
-          <widget class="QSpinBox" name="vibrationSpin">
+          <widget class="QPushButton" name="vibrationButton">
            <property name="minimumSize">
             <size>
-             <width>65</width>
-             <height>21</height>
+             <width>68</width>
+             <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>65</width>
+             <width>68</width>
              <height>16777215</height>
             </size>
            </property>
-           <property name="suffix">
-            <string>%</string>
-           </property>
-           <property name="minimum">
-            <number>1</number>
-           </property>
-           <property name="maximum">
-            <number>200</number>
+           <property name="styleSheet">
+            <string notr="true">min-width: 68px;</string>
            </property>
-           <property name="value">
-            <number>100</number>
+           <property name="text">
+            <string>Configure</string>
            </property>
           </widget>
          </item>
@@ -250,18 +244,18 @@
           <widget class="QPushButton" name="motionButton">
            <property name="minimumSize">
             <size>
-             <width>57</width>
+             <width>68</width>
              <height>0</height>
             </size>
            </property>
            <property name="maximumSize">
             <size>
-             <width>55</width>
+             <width>68</width>
              <height>16777215</height>
             </size>
            </property>
            <property name="styleSheet">
-            <string notr="true">min-width: 55px;</string>
+            <string notr="true">min-width: 68px;</string>
            </property>
            <property name="text">
             <string>Configure</string>
@@ -272,7 +266,7 @@
        </widget>
       </item>
       <item alignment="Qt::AlignVCenter">
-       <widget class="QWidget" name="widget" native="true">
+       <widget class="QWidget" name="connectedControllers" native="true">
         <layout class="QGridLayout" name="gridLayout_2">
          <property name="leftMargin">
           <number>5</number>
@@ -468,13 +462,13 @@
         </property>
         <property name="minimumSize">
          <size>
-          <width>57</width>
+          <width>68</width>
           <height>0</height>
          </size>
         </property>
         <property name="maximumSize">
          <size>
-          <width>55</width>
+          <width>68</width>
           <height>16777215</height>
          </size>
         </property>
@@ -494,7 +488,7 @@
          <enum>Qt::LeftToRight</enum>
         </property>
         <property name="styleSheet">
-         <string notr="true">min-width: 55px;</string>
+         <string notr="true">min-width: 68px;</string>
         </property>
         <property name="text">
          <string>Defaults</string>
@@ -511,13 +505,13 @@
         </property>
         <property name="minimumSize">
          <size>
-          <width>57</width>
+          <width>68</width>
           <height>0</height>
          </size>
         </property>
         <property name="maximumSize">
          <size>
-          <width>55</width>
+          <width>68</width>
           <height>16777215</height>
          </size>
         </property>
@@ -537,7 +531,7 @@
          <enum>Qt::LeftToRight</enum>
         </property>
         <property name="styleSheet">
-         <string notr="true">min-width: 55px;</string>
+         <string notr="true">min-width: 68px;</string>
         </property>
         <property name="text">
          <string>Clear</string>

+ 7 - 7
src/yuzu/configuration/configure_input_advanced.cpp

@@ -68,8 +68,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
         for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
             connect(color_buttons[button_idx], &QPushButton::clicked, this,
                     [this, player_idx, button_idx] {
-                        OnControllerButtonClick(static_cast<int>(player_idx),
-                                                static_cast<int>(button_idx));
+                        OnControllerButtonClick(player_idx, button_idx);
                     });
         }
     }
@@ -94,20 +93,21 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
 
 ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
 
-void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) {
+void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx,
+                                                     std::size_t button_idx) {
     const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
     if (!new_bg_color.isValid()) {
         return;
     }
     controllers_colors[player_idx][button_idx] = new_bg_color;
     controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
-        QStringLiteral("background-color: %1; min-width: 55px;")
+        QStringLiteral("background-color: %1; min-width: 60px;")
             .arg(controllers_colors[player_idx][button_idx].name()));
 }
 
 void ConfigureInputAdvanced::ApplyConfiguration() {
     for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
-        auto& player = Settings::values.players[player_idx];
+        auto& player = Settings::values.players.GetValue()[player_idx];
         std::array<u32, 4> colors{};
         std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
                        colors.begin(), [](QColor color) { return color.rgb(); });
@@ -126,7 +126,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
 
 void ConfigureInputAdvanced::LoadConfiguration() {
     for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
-        auto& player = Settings::values.players[player_idx];
+        auto& player = Settings::values.players.GetValue()[player_idx];
         std::array<u32, 4> colors = {
             player.body_color_left,
             player.button_color_left,
@@ -139,7 +139,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
 
         for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
             controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
-                QStringLiteral("background-color: %1; min-width: 55px;")
+                QStringLiteral("background-color: %1; min-width: 60px;")
                     .arg(controllers_colors[player_idx][button_idx].name()));
         }
     }

+ 1 - 1
src/yuzu/configuration/configure_input_advanced.h

@@ -35,7 +35,7 @@ private:
     void RetranslateUI();
     void UpdateUIEnabled();
 
-    void OnControllerButtonClick(int player_idx, int button_idx);
+    void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx);
 
     void LoadConfiguration();
 

+ 96 - 96
src/yuzu/configuration/configure_input_advanced.ui

@@ -192,18 +192,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -247,18 +247,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -323,18 +323,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -378,18 +378,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -478,18 +478,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -533,18 +533,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -609,18 +609,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -664,18 +664,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -782,18 +782,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -837,18 +837,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -913,18 +913,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -968,18 +968,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1068,18 +1068,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1123,18 +1123,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1199,18 +1199,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1254,18 +1254,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1393,18 +1393,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1448,18 +1448,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1524,18 +1524,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1579,18 +1579,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1679,18 +1679,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1734,18 +1734,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1810,18 +1810,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1865,18 +1865,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -1983,18 +1983,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2038,18 +2038,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2114,18 +2114,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2169,18 +2169,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2269,18 +2269,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2324,18 +2324,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2400,18 +2400,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>
@@ -2455,18 +2455,18 @@
                              </property>
                              <property name="minimumSize">
                               <size>
-                               <width>57</width>
+                               <width>68</width>
                                <height>0</height>
                               </size>
                              </property>
                              <property name="maximumSize">
                               <size>
-                               <width>55</width>
+                               <width>68</width>
                                <height>16777215</height>
                               </size>
                              </property>
                              <property name="styleSheet">
-                              <string notr="true">min-width: 55px;</string>
+                              <string notr="true">min-width: 68px;</string>
                              </property>
                              <property name="text">
                               <string/>

+ 0 - 37
src/yuzu/configuration/configure_input_dialog.cpp

@@ -1,37 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "ui_configure_input_dialog.h"
-#include "yuzu/configuration/configure_input_dialog.h"
-
-ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players,
-                                           InputCommon::InputSubsystem* input_subsystem)
-    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()),
-      input_widget(new ConfigureInput(this)) {
-    ui->setupUi(this);
-
-    input_widget->Initialize(input_subsystem, max_players);
-
-    ui->inputLayout->addWidget(input_widget);
-
-    RetranslateUI();
-}
-
-ConfigureInputDialog::~ConfigureInputDialog() = default;
-
-void ConfigureInputDialog::ApplyConfiguration() {
-    input_widget->ApplyConfiguration();
-}
-
-void ConfigureInputDialog::changeEvent(QEvent* event) {
-    if (event->type() == QEvent::LanguageChange) {
-        RetranslateUI();
-    }
-
-    QDialog::changeEvent(event);
-}
-
-void ConfigureInputDialog::RetranslateUI() {
-    ui->retranslateUi(this);
-}

+ 0 - 38
src/yuzu/configuration/configure_input_dialog.h

@@ -1,38 +0,0 @@
-// Copyright 2020 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <memory>
-#include <QDialog>
-#include "yuzu/configuration/configure_input.h"
-
-class QPushButton;
-
-namespace InputCommon {
-class InputSubsystem;
-}
-
-namespace Ui {
-class ConfigureInputDialog;
-}
-
-class ConfigureInputDialog : public QDialog {
-    Q_OBJECT
-
-public:
-    explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players,
-                                  InputCommon::InputSubsystem* input_subsystem);
-    ~ConfigureInputDialog() override;
-
-    void ApplyConfiguration();
-
-private:
-    void changeEvent(QEvent* event) override;
-    void RetranslateUI();
-
-    std::unique_ptr<Ui::ConfigureInputDialog> ui;
-
-    ConfigureInput* input_widget;
-};

+ 398 - 168
src/yuzu/configuration/configure_input_player.cpp

@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <thread>
 #include <utility>
 #include <QGridLayout>
 #include <QInputDialog>
@@ -22,8 +23,9 @@
 #include "ui_configure_input_player.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_input_player.h"
-
-constexpr std::size_t HANDHELD_INDEX = 8;
+#include "yuzu/configuration/configure_vibration.h"
+#include "yuzu/configuration/input_profiles.h"
+#include "yuzu/util/limitable_input_dialog.h"
 
 const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
     ConfigureInputPlayer::analog_sub_buttons{{
@@ -35,6 +37,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
 
 namespace {
 
+constexpr std::size_t HANDHELD_INDEX = 8;
+
 void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
                       bool connected) {
     Core::System& system{Core::System::GetInstance()};
@@ -240,10 +244,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
 ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
                                            QWidget* bottom_row,
                                            InputCommon::InputSubsystem* input_subsystem_,
-                                           bool debug)
+                                           InputProfiles* profiles_, bool debug)
     : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
-      debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()),
-      poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) {
+      debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
+      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
+      bottom_row(bottom_row) {
     ui->setupUi(this);
 
     setFocusPolicy(Qt::ClickFocus);
@@ -366,6 +371,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
             }
 
             connect(analog_button, &QPushButton::clicked, [=, this] {
+                if (!map_analog_stick_accepted) {
+                    map_analog_stick_accepted =
+                        QMessageBox::information(
+                            this, tr("Map Analog Stick"),
+                            tr("After pressing OK, first move your joystick horizontally, and then "
+                               "vertically.\nTo invert the axes, first move your joystick "
+                               "vertically, and then horizontally."),
+                            QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
+                    if (!map_analog_stick_accepted) {
+                        return;
+                    }
+                }
                 HandleClick(
                     analog_map_buttons[analog_id][sub_button_id],
                     [=, this](const Common::ParamPackage& params) {
@@ -455,11 +472,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
                 });
     }
 
+    if (debug || player_index == 9) {
+        ui->groupConnectedController->setCheckable(false);
+    }
+
     // The Debug Controller can only choose the Pro Controller.
     if (debug) {
         ui->buttonScreenshot->setEnabled(false);
         ui->buttonHome->setEnabled(false);
-        ui->groupConnectedController->setCheckable(false);
         QStringList debug_controller_types = {
             tr("Pro Controller"),
         };
@@ -477,11 +497,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         UpdateMotionButtons();
     });
 
-    connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
+    connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
             &ConfigureInputPlayer::UpdateMappingWithDefaults);
 
+    ui->comboDevices->setCurrentIndex(-1);
+
     ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
-    UpdateInputDevices();
     connect(ui->buttonRefreshDevices, &QPushButton::clicked,
             [this] { emit RefreshInputDevices(); });
 
@@ -492,14 +513,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         Common::ParamPackage params;
         if (input_subsystem->GetGCButtons()->IsPolling()) {
             params = input_subsystem->GetGCButtons()->GetNextInput();
-            if (params.Has("engine")) {
+            if (params.Has("engine") && IsInputAcceptable(params)) {
                 SetPollingResult(params, false);
                 return;
             }
         }
         if (input_subsystem->GetGCAnalogs()->IsPolling()) {
             params = input_subsystem->GetGCAnalogs()->GetNextInput();
-            if (params.Has("engine")) {
+            if (params.Has("engine") && IsInputAcceptable(params)) {
                 SetPollingResult(params, false);
                 return;
             }
@@ -513,13 +534,24 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
         }
         for (auto& poller : device_pollers) {
             params = poller->GetNextInput();
-            if (params.Has("engine")) {
+            if (params.Has("engine") && IsInputAcceptable(params)) {
                 SetPollingResult(params, false);
                 return;
             }
         }
     });
 
+    UpdateInputProfiles();
+
+    connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
+            &ConfigureInputPlayer::CreateProfile);
+    connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
+            &ConfigureInputPlayer::DeleteProfile);
+    connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
+            &ConfigureInputPlayer::LoadProfile);
+    connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
+            &ConfigureInputPlayer::SaveProfile);
+
     LoadConfiguration();
 
     // TODO(wwylele): enable this when we actually emulate it
@@ -529,7 +561,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
 ConfigureInputPlayer::~ConfigureInputPlayer() = default;
 
 void ConfigureInputPlayer::ApplyConfiguration() {
-    auto& player = Settings::values.players[player_index];
+    auto& player = Settings::values.players.GetValue()[player_index];
     auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
     auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
 
@@ -543,33 +575,58 @@ void ConfigureInputPlayer::ApplyConfiguration() {
     }
 
     auto& motions = player.motions;
+
     std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
                    [](const Common::ParamPackage& param) { return param.Serialize(); });
 
-    player.controller_type =
-        static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
-    player.connected = ui->groupConnectedController->isChecked();
+    const auto controller_type =
+        GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+    const auto player_connected = ui->groupConnectedController->isChecked() &&
+                                  controller_type != Settings::ControllerType::Handheld;
 
-    // Player 2-8
-    if (player_index != 0) {
-        UpdateController(player.controller_type, player_index, player.connected);
+    if (player.controller_type == controller_type && player.connected == player_connected) {
+        // Set vibration devices in the event that the input device has changed.
+        ConfigureVibration::SetVibrationDevices(player_index);
         return;
     }
 
-    // Player 1 and Handheld
-    auto& handheld = Settings::values.players[HANDHELD_INDEX];
-    // If Handheld is selected, copy all the settings from Player 1 to Handheld.
-    if (player.controller_type == Settings::ControllerType::Handheld) {
-        handheld = player;
-        handheld.connected = ui->groupConnectedController->isChecked();
-        player.connected = false; // Disconnect Player 1
-    } else {
-        player.connected = ui->groupConnectedController->isChecked();
-        handheld.connected = false; // Disconnect Handheld
+    // Disconnect the controller first.
+    UpdateController(controller_type, player_index, false);
+
+    player.controller_type = controller_type;
+    player.connected = player_connected;
+
+    ConfigureVibration::SetVibrationDevices(player_index);
+
+    // Handheld
+    if (player_index == 0) {
+        auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
+        if (controller_type == Settings::ControllerType::Handheld) {
+            handheld = player;
+        }
+        handheld.connected = ui->groupConnectedController->isChecked() &&
+                             controller_type == Settings::ControllerType::Handheld;
+        UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
+    }
+
+    if (!player.connected) {
+        return;
     }
 
-    UpdateController(player.controller_type, player_index, player.connected);
-    UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
+    // This emulates a delay between disconnecting and reconnecting controllers as some games
+    // do not respond to a change in controller type if it was instantaneous.
+    using namespace std::chrono_literals;
+    std::this_thread::sleep_for(20ms);
+
+    UpdateController(controller_type, player_index, player_connected);
+}
+
+void ConfigureInputPlayer::showEvent(QShowEvent* event) {
+    if (bottom_row == nullptr) {
+        return;
+    }
+    QWidget::showEvent(event);
+    ui->main->addWidget(bottom_row);
 }
 
 void ConfigureInputPlayer::changeEvent(QEvent* event) {
@@ -586,7 +643,7 @@ void ConfigureInputPlayer::RetranslateUI() {
 }
 
 void ConfigureInputPlayer::LoadConfiguration() {
-    auto& player = Settings::values.players[player_index];
+    auto& player = Settings::values.players.GetValue()[player_index];
     if (debug) {
         std::transform(Settings::values.debug_pad_buttons.begin(),
                        Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@@ -604,6 +661,7 @@ void ConfigureInputPlayer::LoadConfiguration() {
     }
 
     UpdateUI();
+    UpdateInputDeviceCombobox();
 
     if (debug) {
         return;
@@ -612,44 +670,75 @@ void ConfigureInputPlayer::LoadConfiguration() {
     ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
     ui->groupConnectedController->setChecked(
         player.connected ||
-        (player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
+        (player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
 }
 
-void ConfigureInputPlayer::UpdateInputDevices() {
-    input_devices = input_subsystem->GetInputDevices();
-    ui->comboDevices->clear();
-    for (auto device : input_devices) {
-        ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
-    }
+void ConfigureInputPlayer::ConnectPlayer(bool connected) {
+    ui->groupConnectedController->setChecked(connected);
 }
 
-void ConfigureInputPlayer::RestoreDefaults() {
-    // Reset Buttons
-    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
-        buttons_param[button_id] = Common::ParamPackage{
-            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
+    // Skip input device persistence if "Input Devices" is set to "Any".
+    if (ui->comboDevices->currentIndex() == 0) {
+        UpdateInputDevices();
+        return;
     }
 
-    // Reset Analogs and Modifier Buttons
-    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
-        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
-            Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
-                Config::default_analogs[analog_id][sub_button_id])};
-            SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
-        }
+    // Find the first button that isn't empty.
+    const auto button_param =
+        std::find_if(buttons_param.begin(), buttons_param.end(),
+                     [](const Common::ParamPackage param) { return param.Has("engine"); });
+    const bool buttons_empty = button_param == buttons_param.end();
 
-        analogs_param[analog_id].Set(
-            "modifier", InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id]));
+    const auto current_engine = button_param->Get("engine", "");
+    const auto current_guid = button_param->Get("guid", "");
+    const auto current_port = button_param->Get("port", "");
+
+    const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
+
+    UpdateInputDevices();
+
+    if (buttons_empty) {
+        return;
     }
 
-    for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
-        motions_param[motion_id] = Common::ParamPackage{
-            InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
+    const bool all_one_device =
+        std::all_of(buttons_param.begin(), buttons_param.end(),
+                    [current_engine, current_guid, current_port,
+                     is_keyboard_mouse](const Common::ParamPackage param) {
+                        if (is_keyboard_mouse) {
+                            return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
+                                   param.Get("engine", "") == "mouse";
+                        }
+                        return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
+                                                        param.Get("guid", "") == current_guid &&
+                                                        param.Get("port", "") == current_port);
+                    });
+
+    if (all_one_device) {
+        if (is_keyboard_mouse) {
+            ui->comboDevices->setCurrentIndex(1);
+            return;
+        }
+        const auto devices_it = std::find_if(
+            input_devices.begin(), input_devices.end(),
+            [current_engine, current_guid, current_port](const Common::ParamPackage param) {
+                return param.Get("class", "") == current_engine &&
+                       param.Get("guid", "") == current_guid &&
+                       param.Get("port", "") == current_port;
+            });
+        const int device_index =
+            devices_it != input_devices.end()
+                ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+                : 0;
+        ui->comboDevices->setCurrentIndex(device_index);
+    } else {
+        ui->comboDevices->setCurrentIndex(0);
     }
+}
 
-    UpdateUI();
-    UpdateInputDevices();
-    ui->comboControllerType->setCurrentIndex(0);
+void ConfigureInputPlayer::RestoreDefaults() {
+    UpdateMappingWithDefaults();
 }
 
 void ConfigureInputPlayer::ClearAll() {
@@ -752,10 +841,167 @@ void ConfigureInputPlayer::UpdateUI() {
     }
 }
 
+void ConfigureInputPlayer::UpdateInputDevices() {
+    input_devices = input_subsystem->GetInputDevices();
+    ui->comboDevices->clear();
+    for (auto device : input_devices) {
+        ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
+    }
+}
+
+void ConfigureInputPlayer::UpdateControllerIcon() {
+    // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
+    // "nonstandard" to use an image through the icon support)
+    const QString stylesheet = [this] {
+        switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+        case Settings::ControllerType::ProController:
+            return QStringLiteral("image: url(:/controller/pro_controller%0)");
+        case Settings::ControllerType::DualJoyconDetached:
+            return QStringLiteral("image: url(:/controller/dual_joycon%0)");
+        case Settings::ControllerType::LeftJoycon:
+            return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
+        case Settings::ControllerType::RightJoycon:
+            return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
+        case Settings::ControllerType::Handheld:
+            return QStringLiteral("image: url(:/controller/handheld%0)");
+        default:
+            return QString{};
+        }
+    }();
+
+    const QString theme = [] {
+        if (QIcon::themeName().contains(QStringLiteral("dark"))) {
+            return QStringLiteral("_dark");
+        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
+            return QStringLiteral("_midnight");
+        } else {
+            return QString{};
+        }
+    }();
+
+    ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
+}
+
+void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
+    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+    if (debug) {
+        layout = Settings::ControllerType::ProController;
+    }
+
+    // List of all the widgets that will be hidden by any of the following layouts that need
+    // "unhidden" after the controller type changes
+    const std::array<QWidget*, 9> layout_show = {
+        ui->buttonShoulderButtonsSLSR,
+        ui->horizontalSpacerShoulderButtonsWidget,
+        ui->horizontalSpacerShoulderButtonsWidget2,
+        ui->buttonShoulderButtonsLeft,
+        ui->buttonMiscButtonsMinusScreenshot,
+        ui->bottomLeft,
+        ui->buttonShoulderButtonsRight,
+        ui->buttonMiscButtonsPlusHome,
+        ui->bottomRight,
+    };
+
+    for (auto* widget : layout_show) {
+        widget->show();
+    }
+
+    std::vector<QWidget*> layout_hidden;
+    switch (layout) {
+    case Settings::ControllerType::ProController:
+    case Settings::ControllerType::DualJoyconDetached:
+    case Settings::ControllerType::Handheld:
+        layout_hidden = {
+            ui->buttonShoulderButtonsSLSR,
+            ui->horizontalSpacerShoulderButtonsWidget2,
+        };
+        break;
+    case Settings::ControllerType::LeftJoycon:
+        layout_hidden = {
+            ui->horizontalSpacerShoulderButtonsWidget2,
+            ui->buttonShoulderButtonsRight,
+            ui->buttonMiscButtonsPlusHome,
+            ui->bottomRight,
+        };
+        break;
+    case Settings::ControllerType::RightJoycon:
+        layout_hidden = {
+            ui->horizontalSpacerShoulderButtonsWidget,
+            ui->buttonShoulderButtonsLeft,
+            ui->buttonMiscButtonsMinusScreenshot,
+            ui->bottomLeft,
+        };
+        break;
+    }
+
+    for (auto* widget : layout_hidden) {
+        widget->hide();
+    }
+}
+
+void ConfigureInputPlayer::UpdateMotionButtons() {
+    if (debug) {
+        // Motion isn't used with the debug controller, hide both groupboxes.
+        ui->buttonMotionLeftGroup->hide();
+        ui->buttonMotionRightGroup->hide();
+        return;
+    }
+
+    // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
+    switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
+    case Settings::ControllerType::ProController:
+    case Settings::ControllerType::LeftJoycon:
+    case Settings::ControllerType::Handheld:
+        // Show "Motion 1" and hide "Motion 2".
+        ui->buttonMotionLeftGroup->show();
+        ui->buttonMotionRightGroup->hide();
+        break;
+    case Settings::ControllerType::RightJoycon:
+        // Show "Motion 2" and hide "Motion 1".
+        ui->buttonMotionLeftGroup->hide();
+        ui->buttonMotionRightGroup->show();
+        break;
+    case Settings::ControllerType::DualJoyconDetached:
+    default:
+        // Show both "Motion 1/2".
+        ui->buttonMotionLeftGroup->show();
+        ui->buttonMotionRightGroup->show();
+        break;
+    }
+}
+
 void ConfigureInputPlayer::UpdateMappingWithDefaults() {
-    if (ui->comboDevices->currentIndex() < 2) {
+    if (ui->comboDevices->currentIndex() == 0) {
         return;
     }
+
+    if (ui->comboDevices->currentIndex() == 1) {
+        // Reset keyboard bindings
+        for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
+            buttons_param[button_id] = Common::ParamPackage{
+                InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+        }
+        for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
+            for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
+                Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+                    Config::default_analogs[analog_id][sub_button_id])};
+                SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
+            }
+
+            analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
+                                                         Config::default_stick_mod[analog_id]));
+        }
+
+        for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
+            motions_param[motion_id] = Common::ParamPackage{
+                InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
+        }
+
+        UpdateUI();
+        return;
+    }
+
+    // Reset controller bindings
     const auto& device = input_devices[ui->comboDevices->currentIndex()];
     auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
     auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
@@ -828,9 +1074,27 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
     }
 
     UpdateUI();
+    UpdateInputDeviceCombobox();
+
     input_setter = std::nullopt;
 }
 
+bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
+    if (ui->comboDevices->currentIndex() == 0) {
+        return true;
+    }
+
+    // Keyboard/Mouse
+    if (ui->comboDevices->currentIndex() == 1) {
+        return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
+    }
+
+    const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
+    return params.Get("engine", "") == current_input_device.Get("class", "") &&
+           params.Get("guid", "") == current_input_device.Get("guid", "") &&
+           params.Get("port", "") == current_input_device.Get("port", "");
+}
+
 void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
     if (!input_setter || !event) {
         return;
@@ -865,135 +1129,101 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
     SetPollingResult({}, true);
 }
 
-void ConfigureInputPlayer::UpdateControllerIcon() {
-    // We aren't using Qt's built in theme support here since we aren't drawing an icon (and its
-    // "nonstandard" to use an image through the icon support)
-    const QString stylesheet = [this] {
-        switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
-        case Settings::ControllerType::ProController:
-            return QStringLiteral("image: url(:/controller/pro_controller%0)");
-        case Settings::ControllerType::DualJoyconDetached:
-            return QStringLiteral("image: url(:/controller/dual_joycon%0)");
-        case Settings::ControllerType::LeftJoycon:
-            return QStringLiteral("image: url(:/controller/single_joycon_left_vertical%0)");
-        case Settings::ControllerType::RightJoycon:
-            return QStringLiteral("image: url(:/controller/single_joycon_right_vertical%0)");
-        case Settings::ControllerType::Handheld:
-            return QStringLiteral("image: url(:/controller/handheld%0)");
-        default:
-            return QString{};
-        }
-    }();
-
-    const QString theme = [this] {
-        if (QIcon::themeName().contains(QStringLiteral("dark"))) {
-            return QStringLiteral("_dark");
-        } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
-            return QStringLiteral("_midnight");
-        } else {
-            return QString{};
-        }
-    }();
+void ConfigureInputPlayer::CreateProfile() {
+    const auto profile_name =
+        LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
 
-    ui->controllerFrame->setStyleSheet(stylesheet.arg(theme));
-}
+    if (profile_name.isEmpty()) {
+        return;
+    }
 
-void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
-    auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
-    if (debug) {
-        layout = Settings::ControllerType::ProController;
+    if (!profiles->IsProfileNameValid(profile_name.toStdString())) {
+        QMessageBox::critical(this, tr("Create Input Profile"),
+                              tr("The given profile name is not valid!"));
+        return;
     }
 
-    // List of all the widgets that will be hidden by any of the following layouts that need
-    // "unhidden" after the controller type changes
-    const std::array<QWidget*, 9> layout_show = {
-        ui->buttonShoulderButtonsSLSR,
-        ui->horizontalSpacerShoulderButtonsWidget,
-        ui->horizontalSpacerShoulderButtonsWidget2,
-        ui->buttonShoulderButtonsLeft,
-        ui->buttonMiscButtonsMinusScreenshot,
-        ui->bottomLeft,
-        ui->buttonShoulderButtonsRight,
-        ui->buttonMiscButtonsPlusHome,
-        ui->bottomRight,
-    };
+    ApplyConfiguration();
 
-    for (auto* widget : layout_show) {
-        widget->show();
+    if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
+        QMessageBox::critical(this, tr("Create Input Profile"),
+                              tr("Failed to create the input profile \"%1\"").arg(profile_name));
+        UpdateInputProfiles();
+        emit RefreshInputProfiles(player_index);
+        return;
     }
 
-    std::vector<QWidget*> layout_hidden;
-    switch (layout) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::DualJoyconDetached:
-    case Settings::ControllerType::Handheld:
-        layout_hidden = {
-            ui->buttonShoulderButtonsSLSR,
-            ui->horizontalSpacerShoulderButtonsWidget2,
-        };
-        break;
-    case Settings::ControllerType::LeftJoycon:
-        layout_hidden = {
-            ui->horizontalSpacerShoulderButtonsWidget2,
-            ui->buttonShoulderButtonsRight,
-            ui->buttonMiscButtonsPlusHome,
-            ui->bottomRight,
-        };
-        break;
-    case Settings::ControllerType::RightJoycon:
-        layout_hidden = {
-            ui->horizontalSpacerShoulderButtonsWidget,
-            ui->buttonShoulderButtonsLeft,
-            ui->buttonMiscButtonsMinusScreenshot,
-            ui->bottomLeft,
-        };
-        break;
+    emit RefreshInputProfiles(player_index);
+
+    ui->comboProfiles->addItem(profile_name);
+    ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
+}
+
+void ConfigureInputPlayer::DeleteProfile() {
+    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+    if (profile_name.isEmpty()) {
+        return;
     }
 
-    for (auto* widget : layout_hidden) {
-        widget->hide();
+    if (!profiles->DeleteProfile(profile_name.toStdString())) {
+        QMessageBox::critical(this, tr("Delete Input Profile"),
+                              tr("Failed to delete the input profile \"%1\"").arg(profile_name));
+        UpdateInputProfiles();
+        emit RefreshInputProfiles(player_index);
+        return;
     }
+
+    emit RefreshInputProfiles(player_index);
+
+    ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
+    ui->comboProfiles->setCurrentIndex(-1);
 }
 
-void ConfigureInputPlayer::UpdateMotionButtons() {
-    if (debug) {
-        // Motion isn't used with the debug controller, hide both groupboxes.
-        ui->buttonMotionLeftGroup->hide();
-        ui->buttonMotionRightGroup->hide();
+void ConfigureInputPlayer::LoadProfile() {
+    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+    if (profile_name.isEmpty()) {
         return;
     }
 
-    // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
-    switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
-    case Settings::ControllerType::ProController:
-    case Settings::ControllerType::LeftJoycon:
-    case Settings::ControllerType::Handheld:
-        // Show "Motion 1" and hide "Motion 2".
-        ui->buttonMotionLeftGroup->show();
-        ui->buttonMotionRightGroup->hide();
-        break;
-    case Settings::ControllerType::RightJoycon:
-        // Show "Motion 2" and hide "Motion 1".
-        ui->buttonMotionLeftGroup->hide();
-        ui->buttonMotionRightGroup->show();
-        break;
-    case Settings::ControllerType::DualJoyconDetached:
-    default:
-        // Show both "Motion 1/2".
-        ui->buttonMotionLeftGroup->show();
-        ui->buttonMotionRightGroup->show();
-        break;
+    ApplyConfiguration();
+
+    if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
+        QMessageBox::critical(this, tr("Load Input Profile"),
+                              tr("Failed to load the input profile \"%1\"").arg(profile_name));
+        UpdateInputProfiles();
+        emit RefreshInputProfiles(player_index);
+        return;
     }
+
+    LoadConfiguration();
 }
 
-void ConfigureInputPlayer::showEvent(QShowEvent* event) {
-    if (bottom_row == nullptr) {
+void ConfigureInputPlayer::SaveProfile() {
+    const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
+
+    if (profile_name.isEmpty()) {
+        return;
+    }
+
+    ApplyConfiguration();
+
+    if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
+        QMessageBox::critical(this, tr("Save Input Profile"),
+                              tr("Failed to save the input profile \"%1\"").arg(profile_name));
+        UpdateInputProfiles();
+        emit RefreshInputProfiles(player_index);
         return;
     }
-    QWidget::showEvent(event);
-    ui->main->addWidget(bottom_row);
 }
 
-void ConfigureInputPlayer::ConnectPlayer(bool connected) {
-    ui->groupConnectedController->setChecked(connected);
+void ConfigureInputPlayer::UpdateInputProfiles() {
+    ui->comboProfiles->clear();
+
+    for (const auto& profile_name : profiles->GetInputProfileNames()) {
+        ui->comboProfiles->addItem(QString::fromStdString(profile_name));
+    }
+
+    ui->comboProfiles->setCurrentIndex(-1);
 }

+ 40 - 9
src/yuzu/configuration/configure_input_player.h

@@ -26,6 +26,8 @@ class QString;
 class QTimer;
 class QWidget;
 
+class InputProfiles;
+
 namespace InputCommon {
 class InputSubsystem;
 }
@@ -45,14 +47,20 @@ class ConfigureInputPlayer : public QWidget {
 public:
     explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
                                   InputCommon::InputSubsystem* input_subsystem_,
-                                  bool debug = false);
+                                  InputProfiles* profiles_, bool debug = false);
     ~ConfigureInputPlayer() override;
 
     /// Save all button configurations to settings file.
     void ApplyConfiguration();
 
+    /// Set the connection state checkbox (used to sync state).
+    void ConnectPlayer(bool connected);
+
     /// Update the input devices combobox.
-    void UpdateInputDevices();
+    void UpdateInputDeviceCombobox();
+
+    /// Updates the list of controller profiles.
+    void UpdateInputProfiles();
 
     /// Restore all buttons to their default values.
     void RestoreDefaults();
@@ -60,9 +68,6 @@ public:
     /// Clear all input configuration.
     void ClearAll();
 
-    /// Set the connection state checkbox (used to sync state).
-    void ConnectPlayer(bool connected);
-
 signals:
     /// Emitted when this controller is connected by the user.
     void Connected(bool connected);
@@ -70,6 +75,12 @@ signals:
     void HandheldStateChanged(bool is_handheld);
     /// Emitted when the input devices combobox is being refreshed.
     void RefreshInputDevices();
+    /**
+     * Emitted when the input profiles combobox is being refreshed.
+     * The player_index represents the current player's index, and the profile combobox
+     * will not be updated for this index as they are already updated by other mechanisms.
+     */
+    void RefreshInputProfiles(std::size_t player_index);
 
 protected:
     void showEvent(QShowEvent* event) override;
@@ -89,6 +100,9 @@ private:
     /// Finish polling and configure input using the input_setter.
     void SetPollingResult(const Common::ParamPackage& params, bool abort);
 
+    /// Checks whether a given input can be accepted.
+    bool IsInputAcceptable(const Common::ParamPackage& params) const;
+
     /// Handle mouse button press events.
     void mousePressEvent(QMouseEvent* event) override;
 
@@ -98,8 +112,8 @@ private:
     /// Update UI to reflect current configuration.
     void UpdateUI();
 
-    /// Update the controller selection combobox
-    void UpdateControllerCombobox();
+    /// Update the available input devices.
+    void UpdateInputDevices();
 
     /// Update the current controller icon.
     void UpdateControllerIcon();
@@ -113,6 +127,18 @@ private:
     /// Gets the default controller mapping for this device and auto configures the input to match.
     void UpdateMappingWithDefaults();
 
+    /// Creates a controller profile.
+    void CreateProfile();
+
+    /// Deletes the selected controller profile.
+    void DeleteProfile();
+
+    /// Loads the selected controller profile.
+    void LoadProfile();
+
+    /// Saves the current controller configuration into a selected controller profile.
+    void SaveProfile();
+
     std::unique_ptr<Ui::ConfigureInputPlayer> ui;
 
     std::size_t player_index;
@@ -120,6 +146,8 @@ private:
 
     InputCommon::InputSubsystem* input_subsystem;
 
+    InputProfiles* profiles;
+
     std::unique_ptr<QTimer> timeout_timer;
     std::unique_ptr<QTimer> poll_timer;
 
@@ -159,12 +187,15 @@ private:
 
     std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
 
+    /// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
+    bool map_analog_stick_accepted{};
+
     /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
     /// keyboard events are ignored.
-    bool want_keyboard_mouse = false;
+    bool want_keyboard_mouse{};
 
     /// List of physical devices users can map with. If a SDL backed device is selected, then you
-    /// can usue this device to get a default mapping.
+    /// can use this device to get a default mapping.
     std::vector<Common::ParamPackage> input_devices;
 
     /// Bottom row is where console wide settings are held, and its "owned" by the parent

+ 130 - 111
src/yuzu/configuration/configure_input_player.ui

@@ -83,6 +83,12 @@
           </property>
           <item>
            <widget class="QComboBox" name="comboControllerType">
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>21</height>
+             </size>
+            </property>
             <item>
              <property name="text">
               <string>Pro Controller</string>
@@ -136,6 +142,12 @@
           </property>
           <item>
            <widget class="QComboBox" name="comboDevices">
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>21</height>
+             </size>
+            </property>
             <item>
              <property name="text">
               <string>Any</string>
@@ -152,14 +164,14 @@
            <widget class="QPushButton" name="buttonRefreshDevices">
             <property name="minimumSize">
              <size>
-              <width>24</width>
-              <height>22</height>
+              <width>21</width>
+              <height>21</height>
              </size>
             </property>
             <property name="maximumSize">
              <size>
-              <width>24</width>
-              <height>22</height>
+              <width>21</width>
+              <height>21</height>
              </size>
             </property>
             <property name="styleSheet">
@@ -198,18 +210,25 @@
            <number>5</number>
           </property>
           <item>
-           <widget class="QComboBox" name="comboProfiles"/>
+           <widget class="QComboBox" name="comboProfiles">
+            <property name="minimumSize">
+             <size>
+              <width>0</width>
+              <height>21</height>
+             </size>
+            </property>
+           </widget>
           </item>
           <item>
            <widget class="QPushButton" name="buttonProfilesSave">
             <property name="maximumSize">
              <size>
-              <width>55</width>
+              <width>68</width>
               <height>16777215</height>
              </size>
             </property>
             <property name="styleSheet">
-             <string notr="true">min-width: 55px;</string>
+             <string notr="true">min-width: 68px;</string>
             </property>
             <property name="text">
              <string>Save</string>
@@ -220,12 +239,12 @@
            <widget class="QPushButton" name="buttonProfilesNew">
             <property name="maximumSize">
              <size>
-              <width>55</width>
+              <width>68</width>
               <height>16777215</height>
              </size>
             </property>
             <property name="styleSheet">
-             <string notr="true">min-width: 55px;</string>
+             <string notr="true">min-width: 68px;</string>
             </property>
             <property name="text">
              <string>New</string>
@@ -236,12 +255,12 @@
            <widget class="QPushButton" name="buttonProfilesDelete">
             <property name="maximumSize">
              <size>
-              <width>55</width>
+              <width>68</width>
               <height>16777215</height>
              </size>
             </property>
             <property name="styleSheet">
-             <string notr="true">min-width: 55px;</string>
+             <string notr="true">min-width: 68px;</string>
             </property>
             <property name="text">
              <string>Delete</string>
@@ -393,18 +412,18 @@
                      <widget class="QPushButton" name="buttonLStickUp">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Up</string>
@@ -463,18 +482,18 @@
                     <widget class="QPushButton" name="buttonLStickLeft">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Left</string>
@@ -512,18 +531,18 @@
                     <widget class="QPushButton" name="buttonLStickRight">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Right</string>
@@ -594,18 +613,18 @@
                      <widget class="QPushButton" name="buttonLStickDown">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Down</string>
@@ -664,18 +683,18 @@
                     <widget class="QPushButton" name="buttonLStick">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Pressed</string>
@@ -713,18 +732,18 @@
                     <widget class="QPushButton" name="buttonLStickMod">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Modifier</string>
@@ -759,13 +778,13 @@
                     <widget class="QSpinBox" name="spinboxLStickRange">
                      <property name="minimumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>21</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
@@ -966,18 +985,18 @@
                      <widget class="QPushButton" name="buttonDpadUp">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Up</string>
@@ -1036,18 +1055,18 @@
                     <widget class="QPushButton" name="buttonDpadLeft">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Left</string>
@@ -1085,18 +1104,18 @@
                     <widget class="QPushButton" name="buttonDpadRight">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Right</string>
@@ -1167,18 +1186,18 @@
                      <widget class="QPushButton" name="buttonDpadDown">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Down</string>
@@ -1292,18 +1311,18 @@
                     <widget class="QPushButton" name="buttonL">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>L</string>
@@ -1341,18 +1360,18 @@
                     <widget class="QPushButton" name="buttonZL">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>ZL</string>
@@ -1445,18 +1464,18 @@
                     <widget class="QPushButton" name="buttonMinus">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Minus</string>
@@ -1494,18 +1513,18 @@
                     <widget class="QPushButton" name="buttonScreenshot">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Capture</string>
@@ -1564,18 +1583,18 @@
                     <widget class="QPushButton" name="buttonPlus">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Plus</string>
@@ -1613,18 +1632,18 @@
                     <widget class="QPushButton" name="buttonHome">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Home</string>
@@ -1717,18 +1736,18 @@
                     <widget class="QPushButton" name="buttonR">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>R</string>
@@ -1766,18 +1785,18 @@
                     <widget class="QPushButton" name="buttonZR">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>ZR</string>
@@ -1870,18 +1889,18 @@
                     <widget class="QPushButton" name="buttonSL">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>SL</string>
@@ -1919,18 +1938,18 @@
                     <widget class="QPushButton" name="buttonSR">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>SR</string>
@@ -2027,18 +2046,18 @@
                  <widget class="QPushButton" name="buttonMotionLeft">
                   <property name="minimumSize">
                    <size>
-                    <width>57</width>
+                    <width>68</width>
                     <height>0</height>
                    </size>
                   </property>
                   <property name="maximumSize">
                    <size>
-                    <width>55</width>
+                    <width>68</width>
                     <height>16777215</height>
                    </size>
                   </property>
                   <property name="styleSheet">
-                   <string notr="true">min-width: 55px;</string>
+                   <string notr="true">min-width: 68px;</string>
                   </property>
                   <property name="text">
                    <string>Left</string>
@@ -2076,18 +2095,18 @@
                  <widget class="QPushButton" name="buttonMotionRight">
                   <property name="minimumSize">
                    <size>
-                    <width>57</width>
+                    <width>68</width>
                     <height>0</height>
                    </size>
                   </property>
                   <property name="maximumSize">
                    <size>
-                    <width>55</width>
+                    <width>68</width>
                     <height>16777215</height>
                    </size>
                   </property>
                   <property name="styleSheet">
-                   <string notr="true">min-width: 55px;</string>
+                   <string notr="true">min-width: 68px;</string>
                   </property>
                   <property name="text">
                    <string>Right</string>
@@ -2225,18 +2244,18 @@
                      <widget class="QPushButton" name="buttonX">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>X</string>
@@ -2295,18 +2314,18 @@
                     <widget class="QPushButton" name="buttonY">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Y</string>
@@ -2344,18 +2363,18 @@
                     <widget class="QPushButton" name="buttonA">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>A</string>
@@ -2426,18 +2445,18 @@
                      <widget class="QPushButton" name="buttonB">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>B</string>
@@ -2580,18 +2599,18 @@
                      <widget class="QPushButton" name="buttonRStickUp">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Up</string>
@@ -2650,18 +2669,18 @@
                     <widget class="QPushButton" name="buttonRStickLeft">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Left</string>
@@ -2699,18 +2718,18 @@
                     <widget class="QPushButton" name="buttonRStickRight">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Right</string>
@@ -2781,18 +2800,18 @@
                      <widget class="QPushButton" name="buttonRStickDown">
                       <property name="minimumSize">
                        <size>
-                        <width>57</width>
+                        <width>68</width>
                         <height>0</height>
                        </size>
                       </property>
                       <property name="maximumSize">
                        <size>
-                        <width>55</width>
+                        <width>68</width>
                         <height>16777215</height>
                        </size>
                       </property>
                       <property name="styleSheet">
-                       <string notr="true">min-width: 55px;</string>
+                       <string notr="true">min-width: 68px;</string>
                       </property>
                       <property name="text">
                        <string>Down</string>
@@ -2851,18 +2870,18 @@
                     <widget class="QPushButton" name="buttonRStick">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Pressed</string>
@@ -2900,18 +2919,18 @@
                     <widget class="QPushButton" name="buttonRStickMod">
                      <property name="minimumSize">
                       <size>
-                       <width>57</width>
+                       <width>68</width>
                        <height>0</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>
                      <property name="styleSheet">
-                      <string notr="true">min-width: 55px;</string>
+                      <string notr="true">min-width: 68px;</string>
                      </property>
                      <property name="text">
                       <string>Modifier</string>
@@ -2946,13 +2965,13 @@
                     <widget class="QSpinBox" name="spinboxRStickRange">
                      <property name="minimumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>21</height>
                       </size>
                      </property>
                      <property name="maximumSize">
                       <size>
-                       <width>55</width>
+                       <width>68</width>
                        <height>16777215</height>
                       </size>
                      </property>

+ 37 - 0
src/yuzu/configuration/configure_input_profile_dialog.cpp

@@ -0,0 +1,37 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "ui_configure_input_profile_dialog.h"
+#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_input_profile_dialog.h"
+
+ConfigureInputProfileDialog::ConfigureInputProfileDialog(
+    QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles)
+    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()),
+      profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) {
+    ui->setupUi(this);
+
+    ui->controllerLayout->addWidget(profile_widget);
+
+    connect(ui->clear_all_button, &QPushButton::clicked, this,
+            [this] { profile_widget->ClearAll(); });
+    connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+            [this] { profile_widget->RestoreDefaults(); });
+
+    RetranslateUI();
+}
+
+ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default;
+
+void ConfigureInputProfileDialog::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QDialog::changeEvent(event);
+}
+
+void ConfigureInputProfileDialog::RetranslateUI() {
+    ui->retranslateUi(this);
+}

+ 40 - 0
src/yuzu/configuration/configure_input_profile_dialog.h

@@ -0,0 +1,40 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QPushButton;
+
+class ConfigureInputPlayer;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class ConfigureInputProfileDialog;
+}
+
+class ConfigureInputProfileDialog : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit ConfigureInputProfileDialog(QWidget* parent,
+                                         InputCommon::InputSubsystem* input_subsystem,
+                                         InputProfiles* profiles);
+    ~ConfigureInputProfileDialog() override;
+
+private:
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    std::unique_ptr<Ui::ConfigureInputProfileDialog> ui;
+
+    ConfigureInputPlayer* profile_widget;
+};

+ 19 - 5
src/yuzu/configuration/configure_input_dialog.ui → src/yuzu/configuration/configure_input_profile_dialog.ui

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>ConfigureInputDialog</class>
- <widget class="QDialog" name="ConfigureInputDialog">
+ <class>ConfigureInputProfileDialog</class>
+ <widget class="QDialog" name="ConfigureInputProfileDialog">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -11,7 +11,7 @@
    </rect>
   </property>
   <property name="windowTitle">
-   <string>Configure Input</string>
+   <string>Create Input Profile</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
@@ -30,10 +30,24 @@
     <number>9</number>
    </property>
    <item>
-    <layout class="QHBoxLayout" name="inputLayout"/>
+    <layout class="QHBoxLayout" name="controllerLayout"/>
    </item>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="clear_all_button">
+       <property name="text">
+        <string>Clear</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="restore_defaults_button">
+       <property name="text">
+        <string>Defaults</string>
+       </property>
+      </widget>
+     </item>
      <item>
       <widget class="QDialogButtonBox" name="buttonBox">
        <property name="standardButtons">
@@ -50,7 +64,7 @@
   <connection>
    <sender>buttonBox</sender>
    <signal>accepted()</signal>
-   <receiver>ConfigureInputDialog</receiver>
+   <receiver>ConfigureInputProfileDialog</receiver>
    <slot>accept()</slot>
   </connection>
  </connections>

+ 0 - 10
src/yuzu/configuration/configure_motion_touch.ui

@@ -312,16 +312,6 @@
    <signal>accepted()</signal>
    <receiver>ConfigureMotionTouch</receiver>
    <slot>ApplyConfiguration()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>220</x>
-     <y>380</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>220</x>
-     <y>200</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 13 - 33
src/yuzu/configuration/configure_mouse_advanced.ui

@@ -15,7 +15,7 @@
   </property>
   <property name="styleSheet">
    <string notr="true">QPushButton {
-  min-width: 55px;
+  min-width: 60px;
 }</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
@@ -42,13 +42,13 @@
          <widget class="QPushButton" name="forward_button">
           <property name="minimumSize">
            <size>
-            <width>57</width>
+            <width>68</width>
             <height>0</height>
            </size>
           </property>
           <property name="maximumSize">
            <size>
-            <width>16777215</width>
+            <width>68</width>
             <height>16777215</height>
            </size>
           </property>
@@ -82,7 +82,7 @@
          <widget class="QPushButton" name="back_button">
           <property name="minimumSize">
            <size>
-            <width>57</width>
+            <width>68</width>
             <height>0</height>
            </size>
           </property>
@@ -110,7 +110,7 @@
          <widget class="QPushButton" name="left_button">
           <property name="minimumSize">
            <size>
-            <width>57</width>
+            <width>68</width>
             <height>0</height>
            </size>
           </property>
@@ -138,13 +138,13 @@
          <widget class="QPushButton" name="middle_button">
           <property name="minimumSize">
            <size>
-            <width>57</width>
+            <width>68</width>
             <height>0</height>
            </size>
           </property>
           <property name="maximumSize">
            <size>
-            <width>16777215</width>
+            <width>68</width>
             <height>16777215</height>
            </size>
           </property>
@@ -204,13 +204,13 @@
          <widget class="QPushButton" name="right_button">
           <property name="minimumSize">
            <size>
-            <width>57</width>
+            <width>68</width>
             <height>0</height>
            </size>
           </property>
           <property name="maximumSize">
            <size>
-            <width>16777215</width>
+            <width>68</width>
             <height>16777215</height>
            </size>
           </property>
@@ -256,13 +256,13 @@
       <widget class="QPushButton" name="buttonClearAll">
        <property name="minimumSize">
         <size>
-         <width>57</width>
+         <width>68</width>
          <height>0</height>
         </size>
        </property>
        <property name="maximumSize">
         <size>
-         <width>16777215</width>
+         <width>68</width>
          <height>16777215</height>
         </size>
        </property>
@@ -275,13 +275,13 @@
       <widget class="QPushButton" name="buttonRestoreDefaults">
        <property name="minimumSize">
         <size>
-         <width>57</width>
+         <width>68</width>
          <height>0</height>
         </size>
        </property>
        <property name="maximumSize">
         <size>
-         <width>16777215</width>
+         <width>68</width>
          <height>16777215</height>
         </size>
        </property>
@@ -324,32 +324,12 @@
    <signal>accepted()</signal>
    <receiver>ConfigureMouseAdvanced</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>124</x>
-     <y>266</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>124</x>
-     <y>143</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>ConfigureMouseAdvanced</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>124</x>
-     <y>266</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>124</x>
-     <y>143</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 2 - 1
src/yuzu/configuration/configure_per_game.cpp

@@ -29,7 +29,8 @@
 
 ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
     : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
-    game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false);
+    game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
+                                           Config::ConfigType::PerGameConfig);
 
     Settings::SetConfiguringGlobal(false);
 

+ 0 - 20
src/yuzu/configuration/configure_per_game.ui

@@ -319,32 +319,12 @@
    <signal>accepted()</signal>
    <receiver>ConfigurePerGame</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>ConfigurePerGame</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 0 - 10
src/yuzu/configuration/configure_touch_from_button.ui

@@ -216,16 +216,6 @@ Drag points to change position, or double-click table cells to edit values.</str
    <signal>rejected()</signal>
    <receiver>ConfigureTouchFromButton</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>249</x>
-     <y>428</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>249</x>
-     <y>224</y>
-    </hint>
-   </hints>
   </connection>
  </connections>
 </ui>

+ 1 - 21
src/yuzu/configuration/configure_touchscreen_advanced.ui

@@ -168,32 +168,12 @@
    <signal>accepted()</signal>
    <receiver>ConfigureTouchscreenAdvanced</receiver>
    <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>140</x>
-     <y>318</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>140</x>
-     <y>169</y>
-    </hint>
-   </hints>
   </connection>
   <connection>
    <sender>buttonBox</sender>
    <signal>rejected()</signal>
    <receiver>ConfigureTouchscreenAdvanced</receiver>
    <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>140</x>
-     <y>318</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>140</x>
-     <y>169</y>
-    </hint>
-   </hints>
-  </connection>
+   </connection>
  </connections>
 </ui>

+ 146 - 0
src/yuzu/configuration/configure_vibration.cpp

@@ -0,0 +1,146 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <unordered_map>
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "core/settings.h"
+#include "ui_configure_vibration.h"
+#include "yuzu/configuration/configure_vibration.h"
+
+ConfigureVibration::ConfigureVibration(QWidget* parent)
+    : QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) {
+    ui->setupUi(this);
+
+    vibration_groupboxes = {
+        ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3,
+        ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6,
+        ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8,
+    };
+
+    vibration_spinboxes = {
+        ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3,
+        ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6,
+        ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8,
+    };
+
+    const auto& players = Settings::values.players.GetValue();
+
+    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+        vibration_groupboxes[i]->setChecked(players[i].vibration_enabled);
+        vibration_spinboxes[i]->setValue(players[i].vibration_strength);
+    }
+
+    ui->checkBoxAccurateVibration->setChecked(
+        Settings::values.enable_accurate_vibrations.GetValue());
+
+    if (!Settings::IsConfiguringGlobal()) {
+        ui->checkBoxAccurateVibration->setDisabled(true);
+    }
+
+    RetranslateUI();
+}
+
+ConfigureVibration::~ConfigureVibration() = default;
+
+void ConfigureVibration::ApplyConfiguration() {
+    auto& players = Settings::values.players.GetValue();
+
+    for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
+        players[i].vibration_enabled = vibration_groupboxes[i]->isChecked();
+        players[i].vibration_strength = vibration_spinboxes[i]->value();
+    }
+
+    Settings::values.enable_accurate_vibrations.SetValue(
+        ui->checkBoxAccurateVibration->isChecked());
+}
+
+void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
+    using namespace Settings::NativeButton;
+    static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{
+        {DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons
+        {A, B, X, Y, R, ZR},                // Right Buttons
+    }};
+
+    auto& player = Settings::values.players.GetValue()[player_index];
+
+    for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) {
+        std::unordered_map<std::string, int> params_count;
+
+        for (const auto button_index : buttons[device_idx]) {
+            const auto& player_button = player.buttons[button_index];
+
+            if (params_count.find(player_button) != params_count.end()) {
+                ++params_count[player_button];
+                continue;
+            }
+
+            params_count.insert_or_assign(player_button, 1);
+        }
+
+        const auto it = std::max_element(
+            params_count.begin(), params_count.end(),
+            [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
+
+        auto& vibration_param_str = player.vibrations[device_idx];
+        vibration_param_str.clear();
+
+        if (it->first.empty()) {
+            continue;
+        }
+
+        const auto param = Common::ParamPackage(it->first);
+
+        const auto engine = param.Get("engine", "");
+        const auto guid = param.Get("guid", "");
+        const auto port = param.Get("port", "");
+
+        if (engine.empty() || engine == "keyboard" || engine == "mouse") {
+            continue;
+        }
+
+        vibration_param_str += fmt::format("engine:{}", engine);
+
+        if (!port.empty()) {
+            vibration_param_str += fmt::format(",port:{}", port);
+        }
+        if (!guid.empty()) {
+            vibration_param_str += fmt::format(",guid:{}", guid);
+        }
+    }
+
+    if (player.vibrations[0] != player.vibrations[1]) {
+        return;
+    }
+
+    if (!player.vibrations[0].empty() &&
+        player.controller_type != Settings::ControllerType::RightJoycon) {
+        player.vibrations[1].clear();
+    } else if (!player.vibrations[1].empty() &&
+               player.controller_type == Settings::ControllerType::RightJoycon) {
+        player.vibrations[0].clear();
+    }
+}
+
+void ConfigureVibration::SetAllVibrationDevices() {
+    // Set vibration devices for all player indices including handheld
+    for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) {
+        SetVibrationDevices(player_idx);
+    }
+}
+
+void ConfigureVibration::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QDialog::changeEvent(event);
+}
+
+void ConfigureVibration::RetranslateUI() {
+    ui->retranslateUi(this);
+}

+ 43 - 0
src/yuzu/configuration/configure_vibration.h

@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+
+class QGroupBox;
+class QSpinBox;
+
+namespace Ui {
+class ConfigureVibration;
+}
+
+class ConfigureVibration : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit ConfigureVibration(QWidget* parent);
+    ~ConfigureVibration() override;
+
+    void ApplyConfiguration();
+
+    static void SetVibrationDevices(std::size_t player_index);
+    static void SetAllVibrationDevices();
+
+private:
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    std::unique_ptr<Ui::ConfigureVibration> ui;
+
+    static constexpr std::size_t NUM_PLAYERS = 8;
+
+    // Groupboxes encapsulating the vibration strength spinbox.
+    std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes;
+
+    // Spinboxes representing the vibration strength percentage.
+    std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes;
+};

+ 546 - 0
src/yuzu/configuration/configure_vibration.ui

@@ -0,0 +1,546 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureVibration</class>
+ <widget class="QDialog" name="ConfigureVibration">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>364</width>
+    <height>242</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Configure Vibration</string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true"/>
+  </property>
+  <layout class="QVBoxLayout">
+   <item>
+    <widget class="QGroupBox" name="vibrationStrengthGroup">
+     <property name="title">
+      <string>Vibration</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0">
+      <property name="leftMargin">
+       <number>9</number>
+      </property>
+      <property name="topMargin">
+       <number>9</number>
+      </property>
+      <property name="rightMargin">
+       <number>9</number>
+      </property>
+      <property name="bottomMargin">
+       <number>9</number>
+      </property>
+      <item>
+       <widget class="QWidget" name="player14Widget" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer1">
+           <property name="title">
+            <string>Player 1</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_8">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer1">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer2">
+           <property name="title">
+            <string>Player 2</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_9">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer2">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer3">
+           <property name="title">
+            <string>Player 3</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_10">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer3">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer4">
+           <property name="title">
+            <string>Player 4</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_11">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer4">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QWidget" name="player58Widget" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer7">
+           <property name="title">
+            <string>Player 5</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_14">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer7">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer8">
+           <property name="title">
+            <string>Player 6</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_15">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer8">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer5">
+           <property name="title">
+            <string>Player 7</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_12">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer5">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item>
+          <widget class="QGroupBox" name="vibrationGroupPlayer6">
+           <property name="title">
+            <string>Player 8</string>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <layout class="QHBoxLayout" name="horizontalLayout_13">
+            <property name="leftMargin">
+             <number>3</number>
+            </property>
+            <property name="topMargin">
+             <number>3</number>
+            </property>
+            <property name="rightMargin">
+             <number>3</number>
+            </property>
+            <property name="bottomMargin">
+             <number>3</number>
+            </property>
+            <item>
+             <widget class="QSpinBox" name="vibrationSpinPlayer6">
+              <property name="minimumSize">
+               <size>
+                <width>68</width>
+                <height>21</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>68</width>
+                <height>16777215</height>
+               </size>
+              </property>
+              <property name="suffix">
+               <string>%</string>
+              </property>
+              <property name="minimum">
+               <number>1</number>
+              </property>
+              <property name="maximum">
+               <number>150</number>
+              </property>
+              <property name="value">
+               <number>100</number>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="vibrationSettingsGroup">
+     <property name="title">
+      <string>Settings</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QCheckBox" name="checkBoxAccurateVibration">
+        <property name="text">
+         <string>Enable Accurate Vibration</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="spacerVibration">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>167</width>
+       <height>55</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBoxVibration">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBoxVibration</sender>
+   <signal>accepted()</signal>
+   <receiver>ConfigureVibration</receiver>
+   <slot>accept()</slot>
+  </connection>
+  <connection>
+   <sender>buttonBoxVibration</sender>
+   <signal>rejected()</signal>
+   <receiver>ConfigureVibration</receiver>
+   <slot>reject()</slot>
+  </connection>
+ </connections>
+</ui>

+ 131 - 0
src/yuzu/configuration/input_profiles.cpp

@@ -0,0 +1,131 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+
+#include "common/common_paths.h"
+#include "common/file_util.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/input_profiles.h"
+
+namespace FS = Common::FS;
+
+namespace {
+
+bool ProfileExistsInFilesystem(std::string_view profile_name) {
+    return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini",
+                                  FS::GetUserPath(FS::UserPath::ConfigDir), profile_name));
+}
+
+bool IsINI(std::string_view filename) {
+    const std::size_t index = filename.rfind('.');
+
+    if (index == std::string::npos) {
+        return false;
+    }
+
+    return filename.substr(index) == ".ini";
+}
+
+std::string GetNameWithoutExtension(const std::string& filename) {
+    const std::size_t index = filename.rfind('.');
+
+    if (index == std::string::npos) {
+        return filename;
+    }
+
+    return filename.substr(0, index);
+}
+
+} // namespace
+
+InputProfiles::InputProfiles() {
+    const std::string input_profile_loc =
+        fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir));
+
+    FS::ForeachDirectoryEntry(
+        nullptr, input_profile_loc,
+        [this](u64* entries_out, const std::string& directory, const std::string& filename) {
+            if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) {
+                map_profiles.insert_or_assign(
+                    GetNameWithoutExtension(filename),
+                    std::make_unique<Config>(GetNameWithoutExtension(filename),
+                                             Config::ConfigType::InputProfile));
+            }
+            return true;
+        });
+}
+
+InputProfiles::~InputProfiles() = default;
+
+std::vector<std::string> InputProfiles::GetInputProfileNames() {
+    std::vector<std::string> profile_names;
+    profile_names.reserve(map_profiles.size());
+
+    for (const auto& [profile_name, config] : map_profiles) {
+        if (!ProfileExistsInFilesystem(profile_name)) {
+            DeleteProfile(profile_name);
+            continue;
+        }
+
+        profile_names.push_back(profile_name);
+    }
+
+    return profile_names;
+}
+
+bool InputProfiles::IsProfileNameValid(std::string_view profile_name) {
+    return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
+}
+
+bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) {
+    if (ProfileExistsInMap(profile_name)) {
+        return false;
+    }
+
+    map_profiles.insert_or_assign(
+        profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
+
+    return SaveProfile(profile_name, player_index);
+}
+
+bool InputProfiles::DeleteProfile(const std::string& profile_name) {
+    if (!ProfileExistsInMap(profile_name)) {
+        return false;
+    }
+
+    if (!ProfileExistsInFilesystem(profile_name) ||
+        FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) {
+        map_profiles.erase(profile_name);
+    }
+
+    return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
+}
+
+bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) {
+    if (!ProfileExistsInMap(profile_name)) {
+        return false;
+    }
+
+    if (!ProfileExistsInFilesystem(profile_name)) {
+        map_profiles.erase(profile_name);
+        return false;
+    }
+
+    map_profiles[profile_name]->ReadControlPlayerValue(player_index);
+    return true;
+}
+
+bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) {
+    if (!ProfileExistsInMap(profile_name)) {
+        return false;
+    }
+
+    map_profiles[profile_name]->SaveControlPlayerValue(player_index);
+    return true;
+}
+
+bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const {
+    return map_profiles.find(profile_name) != map_profiles.end();
+}

+ 32 - 0
src/yuzu/configuration/input_profiles.h

@@ -0,0 +1,32 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+class Config;
+
+class InputProfiles {
+
+public:
+    explicit InputProfiles();
+    virtual ~InputProfiles();
+
+    std::vector<std::string> GetInputProfileNames();
+
+    static bool IsProfileNameValid(std::string_view profile_name);
+
+    bool CreateProfile(const std::string& profile_name, std::size_t player_index);
+    bool DeleteProfile(const std::string& profile_name);
+    bool LoadProfile(const std::string& profile_name, std::size_t player_index);
+    bool SaveProfile(const std::string& profile_name, std::size_t player_index);
+
+private:
+    bool ProfileExistsInMap(const std::string& profile_name) const;
+
+    std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
+};

+ 45 - 11
src/yuzu/main.cpp

@@ -18,6 +18,7 @@
 #include "applets/web_browser.h"
 #include "configuration/configure_input.h"
 #include "configuration/configure_per_game.h"
+#include "configuration/configure_vibration.h"
 #include "core/file_sys/vfs.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/frontend/applets/controller.h"
@@ -50,12 +51,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include <QDesktopServices>
 #include <QDesktopWidget>
 #include <QDialogButtonBox>
+#include <QDir>
 #include <QFile>
 #include <QFileDialog>
 #include <QInputDialog>
 #include <QMessageBox>
 #include <QProgressBar>
 #include <QProgressDialog>
+#include <QPushButton>
 #include <QShortcut>
 #include <QStatusBar>
 #include <QSysInfo>
@@ -277,6 +280,8 @@ GMainWindow::GMainWindow()
     if (args.length() >= 2) {
         BootGame(args[1]);
     }
+
+    MigrateConfigFiles();
 }
 
 GMainWindow::~GMainWindow() {
@@ -288,6 +293,7 @@ GMainWindow::~GMainWindow() {
 void GMainWindow::ControllerSelectorReconfigureControllers(
     const Core::Frontend::ControllerParameters& parameters) {
     QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
+
     dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
                           Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
     dialog.setWindowModality(Qt::WindowModal);
@@ -547,13 +553,14 @@ void GMainWindow::InitializeWidgets() {
     dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
     dock_status_button->setFocusPolicy(Qt::NoFocus);
     connect(dock_status_button, &QPushButton::clicked, [&] {
-        Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
-        dock_status_button->setChecked(Settings::values.use_docked_mode);
-        OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode);
+        Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue());
+        dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+        OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+                            Settings::values.use_docked_mode.GetValue());
     });
     dock_status_button->setText(tr("DOCK"));
     dock_status_button->setCheckable(true);
-    dock_status_button->setChecked(Settings::values.use_docked_mode);
+    dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
     statusBar()->insertPermanentWidget(0, dock_status_button);
 
     // Setup ASync button
@@ -792,10 +799,11 @@ void GMainWindow::InitializeHotkeys() {
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this),
             &QShortcut::activated, this, [&] {
-                Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
-                OnDockedModeChanged(!Settings::values.use_docked_mode,
-                                    Settings::values.use_docked_mode);
-                dock_status_button->setChecked(Settings::values.use_docked_mode);
+                Settings::values.use_docked_mode.SetValue(
+                    !Settings::values.use_docked_mode.GetValue());
+                OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
+                                    Settings::values.use_docked_mode.GetValue());
+                dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
             &QShortcut::activated, this,
@@ -1087,9 +1095,11 @@ void GMainWindow::BootGame(const QString& filename) {
     const auto loader = Loader::GetLoader(v_file);
     if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
         // Load per game settings
-        Config per_game_config(fmt::format("{:016X}.ini", title_id), false);
+        Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig);
     }
 
+    ConfigureVibration::SetAllVibrationDevices();
+
     Settings::LogSettings();
 
     if (UISettings::values.select_user_on_boot) {
@@ -1577,7 +1587,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
     const QString config_dir =
         QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
     const QString custom_config_file_path =
-        config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id));
+        config_dir + QStringLiteral("custom") + QDir::separator() +
+        QString::fromStdString(fmt::format("{:016X}.ini", program_id));
 
     if (!QFile::exists(custom_config_file_path)) {
         QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
@@ -2393,6 +2404,29 @@ void GMainWindow::OnCaptureScreenshot() {
     OnStartGame();
 }
 
+// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
+void GMainWindow::MigrateConfigFiles() {
+    const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir);
+    const QDir config_dir = QDir(QString::fromStdString(config_dir_str));
+    const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini")));
+
+    Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str));
+    for (QStringList::const_iterator it = config_dir_list.constBegin();
+         it != config_dir_list.constEnd(); ++it) {
+        const auto filename = it->toStdString();
+        if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) {
+            continue;
+        }
+        const auto origin = fmt::format("{}{}", config_dir_str, filename);
+        const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename);
+        LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
+        if (!Common::FS::Rename(origin, destination)) {
+            // Delete the old config file if one already exists in the new location.
+            Common::FS::Delete(origin);
+        }
+    }
+}
+
 void GMainWindow::UpdateWindowTitle(const std::string& title_name,
                                     const std::string& title_version) {
     const auto full_name = std::string(Common::g_build_fullname);
@@ -2450,7 +2484,7 @@ void GMainWindow::UpdateStatusBar() {
 }
 
 void GMainWindow::UpdateStatusButtons() {
-    dock_status_button->setChecked(Settings::values.use_docked_mode);
+    dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
     multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
     Settings::values.use_asynchronous_gpu_emulation.SetValue(
         Settings::values.use_asynchronous_gpu_emulation.GetValue() ||

+ 1 - 0
src/yuzu/main.h

@@ -251,6 +251,7 @@ private:
     std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
     InstallResult InstallNSPXCI(const QString& filename);
     InstallResult InstallNCA(const QString& filename);
+    void MigrateConfigFiles();
     void UpdateWindowTitle(const std::string& title_name = {},
                            const std::string& title_version = {});
     void UpdateStatusBar();

+ 15 - 12
src/yuzu_cmd/config.cpp

@@ -228,24 +228,24 @@ static const std::array<int, 8> keyboard_mods{
 
 void Config::ReadValues() {
     // Controls
-    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
         const auto group = fmt::format("ControlsP{}", p);
         for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
             std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-            Settings::values.players[p].buttons[i] =
+            Settings::values.players.GetValue()[p].buttons[i] =
                 sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
-            if (Settings::values.players[p].buttons[i].empty())
-                Settings::values.players[p].buttons[i] = default_param;
+            if (Settings::values.players.GetValue()[p].buttons[i].empty())
+                Settings::values.players.GetValue()[p].buttons[i] = default_param;
         }
 
         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
             std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
                 default_analogs[i][3], default_analogs[i][4], 0.5f);
-            Settings::values.players[p].analogs[i] =
+            Settings::values.players.GetValue()[p].analogs[i] =
                 sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
-            if (Settings::values.players[p].analogs[i].empty())
-                Settings::values.players[p].analogs[i] = default_param;
+            if (Settings::values.players.GetValue()[p].analogs[i].empty())
+                Settings::values.players.GetValue()[p].analogs[i] = default_param;
         }
     }
 
@@ -288,10 +288,12 @@ void Config::ReadValues() {
             Settings::values.debug_pad_analogs[i] = default_param;
     }
 
-    Settings::values.vibration_enabled =
-        sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
-    Settings::values.motion_enabled =
-        sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true);
+    Settings::values.vibration_enabled.SetValue(
+        sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true));
+    Settings::values.enable_accurate_vibrations.SetValue(
+        sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false));
+    Settings::values.motion_enabled.SetValue(
+        sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true));
     Settings::values.touchscreen.enabled =
         sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
     Settings::values.touchscreen.device =
@@ -343,7 +345,8 @@ void Config::ReadValues() {
     Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
 
     // System
-    Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
+    Settings::values.use_docked_mode.SetValue(
+        sdl2_config->GetBoolean("System", "use_docked_mode", false));
     const auto size = sdl2_config->GetInteger("System", "users_size", 0);
 
     Settings::values.current_user = std::clamp<int>(

+ 8 - 0
src/yuzu_cmd/default_ini.h

@@ -65,6 +65,14 @@ button_screenshot=
 lstick=
 rstick=
 
+# Whether to enable or disable vibration
+# 0: Disabled, 1 (default): Enabled
+vibration_enabled=
+
+# Whether to enable or disable accurate vibrations
+# 0 (default): Disabled, 1: Enabled
+enable_accurate_vibrations=
+
 # for motion input, the following devices are available:
 #  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
 #      - "update_period": update period in milliseconds (default to 100)

+ 8 - 7
src/yuzu_tester/config.cpp

@@ -47,13 +47,13 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
 
 void Config::ReadValues() {
     // Controls
-    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
         for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-            Settings::values.players[p].buttons[i] = "";
+            Settings::values.players.GetValue()[p].buttons[i] = "";
         }
 
         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-            Settings::values.players[p].analogs[i] = "";
+            Settings::values.players.GetValue()[p].analogs[i] = "";
         }
     }
 
@@ -75,8 +75,9 @@ void Config::ReadValues() {
         Settings::values.debug_pad_analogs[i] = "";
     }
 
-    Settings::values.vibration_enabled = true;
-    Settings::values.motion_enabled = true;
+    Settings::values.vibration_enabled.SetValue(true);
+    Settings::values.enable_accurate_vibrations.SetValue(false);
+    Settings::values.motion_enabled.SetValue(true);
     Settings::values.touchscreen.enabled = "";
     Settings::values.touchscreen.device = "";
     Settings::values.touchscreen.finger = 0;
@@ -84,8 +85,8 @@ void Config::ReadValues() {
     Settings::values.touchscreen.diameter_x = 15;
     Settings::values.touchscreen.diameter_y = 15;
 
-    Settings::values.use_docked_mode =
-        sdl2_config->GetBoolean("Controls", "use_docked_mode", false);
+    Settings::values.use_docked_mode.SetValue(
+        sdl2_config->GetBoolean("Controls", "use_docked_mode", false));
 
     // Data Storage
     Settings::values.use_virtual_sd =

Некоторые файлы не были показаны из-за большого количества измененных файлов