diff options
78 files changed, 3570 insertions, 1647 deletions
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index b6dd2063d..836dd25ca 100644 --- a/dist/qt_themes/default/style.qss +++ b/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 { diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 66026e8be..2a1e8ddeb 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/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 { diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index c6318ba4e..70e540b06 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/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, diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp index e5d3090d5..bccea0894 100644 --- a/src/common/page_table.cpp +++ b/src/common/page_table.cpp @@ -8,7 +8,7 @@ namespace Common { PageTable::PageTable() = default; -PageTable::~PageTable() = default; +PageTable::~PageTable() noexcept = default; void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, bool has_attribute) { diff --git a/src/common/page_table.h b/src/common/page_table.h index cf5eed780..9754fabf9 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -4,9 +4,7 @@ #pragma once -#include <vector> - -#include <boost/icl/interval_map.hpp> +#include <tuple> #include "common/common_types.h" #include "common/memory_hook.h" @@ -51,13 +49,21 @@ struct SpecialRegion { */ struct PageTable { PageTable(); - ~PageTable(); + ~PageTable() noexcept; + + PageTable(const PageTable&) = delete; + PageTable& operator=(const PageTable&) = delete; + + PageTable(PageTable&&) noexcept = default; + PageTable& operator=(PageTable&&) noexcept = default; /** * Resizes the page table to be able to accomodate enough pages within * a given address space. * * @param address_space_width_in_bits The address size width in bits. + * @param page_size_in_bits The page size in bits. + * @param has_attribute Whether or not this page has any backing attributes. */ void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, bool has_attribute); diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp index b009cb500..e3ca29258 100644 --- a/src/common/virtual_buffer.cpp +++ b/src/common/virtual_buffer.cpp @@ -13,7 +13,7 @@ namespace Common { -void* AllocateMemoryPages(std::size_t size) { +void* AllocateMemoryPages(std::size_t size) noexcept { #ifdef _WIN32 void* base{VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE)}; #else @@ -29,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) { return base; } -void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) { +void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { if (!base) { return; } diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h index 125cb42f0..91d430036 100644 --- a/src/common/virtual_buffer.h +++ b/src/common/virtual_buffer.h @@ -4,29 +4,53 @@ #pragma once -#include "common/common_funcs.h" +#include <type_traits> +#include <utility> namespace Common { -void* AllocateMemoryPages(std::size_t size); -void FreeMemoryPages(void* base, std::size_t size); +void* AllocateMemoryPages(std::size_t size) noexcept; +void FreeMemoryPages(void* base, std::size_t size) noexcept; template <typename T> -class VirtualBuffer final : NonCopyable { +class VirtualBuffer final { public: + static_assert( + std::is_trivially_constructible_v<T>, + "T must be trivially constructible, as non-trivial constructors will not be executed " + "with the current allocator"); + constexpr VirtualBuffer() = default; explicit VirtualBuffer(std::size_t count) : alloc_size{count * sizeof(T)} { base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); } - ~VirtualBuffer() { + ~VirtualBuffer() noexcept { FreeMemoryPages(base_ptr, alloc_size); } + VirtualBuffer(const VirtualBuffer&) = delete; + VirtualBuffer& operator=(const VirtualBuffer&) = delete; + + VirtualBuffer(VirtualBuffer&& other) noexcept + : alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), + nullptr} {} + + VirtualBuffer& operator=(VirtualBuffer&& other) noexcept { + alloc_size = std::exchange(other.alloc_size, 0); + base_ptr = std::exchange(other.base_ptr, nullptr); + return *this; + } + void resize(std::size_t count) { + const auto new_size = count * sizeof(T); + if (new_size == alloc_size) { + return; + } + FreeMemoryPages(base_ptr, alloc_size); - alloc_size = count * sizeof(T); + alloc_size = new_size; base_ptr = reinterpret_cast<T*>(AllocateMemoryPages(alloc_size)); } diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 5582091f4..03bbedf8b 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/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); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 1acc82497..b9a270a55 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/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 { diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 277b70e53..25ac5af46 100644 --- a/src/core/frontend/input.h +++ b/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 {}; } }; @@ -122,6 +122,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. * diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 2ce742e35..eb097738a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/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}; diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index 2151da783..3ca63f020 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/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 { @@ -62,7 +62,7 @@ void Controller::Initialize() { common_args.play_startup_sound, common_args.size, common_args.system_tick, common_args.theme_color); - library_applet_version = LibraryAppletVersion{common_args.library_version}; + controller_applet_version = ControllerAppletVersion{common_args.library_version}; const auto private_arg_storage = broker.PopNormalDataToApplet(); ASSERT(private_arg_storage != nullptr); @@ -70,39 +70,78 @@ void Controller::Initialize() { const auto& private_arg = private_arg_storage->GetData(); ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); - std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate)); + std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size()); ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), "Unknown ControllerSupportArgPrivate revision={} with size={}", - library_applet_version, controller_private_arg.arg_private_size); + controller_applet_version, controller_private_arg.arg_private_size); + + // Some games such as Cave Story+ set invalid values for the ControllerSupportMode. + // Defer to arg_size to set the ControllerSupportMode. + if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) { + switch (controller_private_arg.arg_size) { + case sizeof(ControllerSupportArgOld): + case sizeof(ControllerSupportArgNew): + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + case sizeof(ControllerUpdateFirmwareArg): + controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate; + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}", + controller_private_arg.mode, controller_private_arg.arg_size); + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + } + } + + // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller. + // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem. + if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) { + if (controller_private_arg.flag_1 && + controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) { + controller_private_arg.caller = ControllerSupportCaller::System; + } else { + controller_private_arg.caller = ControllerSupportCaller::Application; + } + } switch (controller_private_arg.mode) { - case ControllerSupportMode::ShowControllerSupport: { + case ControllerSupportMode::ShowControllerSupport: + case ControllerSupportMode::ShowControllerStrapGuide: { const auto user_arg_storage = broker.PopNormalDataToApplet(); ASSERT(user_arg_storage != nullptr); const auto& user_arg = user_arg_storage->GetData(); - switch (library_applet_version) { - case LibraryAppletVersion::Version3: - case LibraryAppletVersion::Version4: - case LibraryAppletVersion::Version5: + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); - std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size()); break; - case LibraryAppletVersion::Version7: + case ControllerAppletVersion::Version7: ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); - std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size()); break; default: UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", - library_applet_version, controller_private_arg.arg_size); + controller_applet_version, controller_private_arg.arg_size); ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); break; } break; } - case ControllerSupportMode::ShowControllerStrapGuide: - case ControllerSupportMode::ShowControllerFirmwareUpdate: + case ControllerSupportMode::ShowControllerFirmwareUpdate: { + const auto update_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(update_arg_storage != nullptr); + + const auto& update_arg = update_arg_storage->GetData(); + ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg)); + + std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size()); + break; + } default: { UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); break; @@ -126,10 +165,10 @@ void Controller::Execute() { switch (controller_private_arg.mode) { case ControllerSupportMode::ShowControllerSupport: { const auto parameters = [this] { - switch (library_applet_version) { - case LibraryAppletVersion::Version3: - case LibraryAppletVersion::Version4: - case LibraryAppletVersion::Version5: + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_old.header, controller_user_arg_old.enable_explain_text, @@ -138,7 +177,7 @@ void Controller::Execute() { controller_user_arg_old.identification_colors.end()), std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(), controller_user_arg_old.explain_text.end())); - case LibraryAppletVersion::Version7: + case ControllerAppletVersion::Version7: default: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_new.header, @@ -170,6 +209,9 @@ void Controller::Execute() { } case ControllerSupportMode::ShowControllerStrapGuide: case ControllerSupportMode::ShowControllerFirmwareUpdate: + UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented", + controller_private_arg.mode); + [[fallthrough]]; default: { ConfigurationComplete(); break; @@ -180,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. diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index f7bb3fba9..a7a1f2b65 100644 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -21,7 +21,7 @@ namespace Service::AM::Applets { using IdentificationColor = std::array<u8, 4>; using ExplainText = std::array<char, 0x81>; -enum class LibraryAppletVersion : u32_le { +enum class ControllerAppletVersion : u32_le { Version3 = 0x3, // 1.0.0 - 2.3.0 Version4 = 0x4, // 3.0.0 - 5.1.0 Version5 = 0x5, // 6.0.0 - 7.0.1 @@ -29,14 +29,18 @@ enum class LibraryAppletVersion : u32_le { }; enum class ControllerSupportMode : u8 { - ShowControllerSupport = 0, - ShowControllerStrapGuide = 1, - ShowControllerFirmwareUpdate = 2, + ShowControllerSupport, + ShowControllerStrapGuide, + ShowControllerFirmwareUpdate, + + MaxControllerSupportMode, }; enum class ControllerSupportCaller : u8 { - Application = 0, - System = 1, + Application, + System, + + MaxControllerSupportCaller, }; struct ControllerSupportArgPrivate { @@ -84,6 +88,13 @@ struct ControllerSupportArgNew { static_assert(sizeof(ControllerSupportArgNew) == 0x430, "ControllerSupportArgNew has incorrect size."); +struct ControllerUpdateFirmwareArg { + bool enable_force_update{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4, + "ControllerUpdateFirmwareArg has incorrect size."); + struct ControllerSupportResultInfo { s8 player_count{}; INSERT_PADDING_BYTES(3); @@ -110,10 +121,11 @@ public: private: const Core::Frontend::ControllerApplet& frontend; - LibraryAppletVersion library_applet_version; + ControllerAppletVersion controller_applet_version; ControllerSupportArgPrivate controller_private_arg; ControllerSupportArgOld controller_user_arg_old; ControllerSupportArgNew controller_user_arg_new; + ControllerUpdateFirmwareArg controller_update_arg; bool complete{false}; ResultCode status{RESULT_SUCCESS}; bool is_single_mode{false}; diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp index 25a886238..ce993bad3 100644 --- a/src/core/hle/service/apm/controller.cpp +++ b/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) { diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index e311bc18c..e2539ded8 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/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; } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index fd5c5a6eb..160dcbbe3 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/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{}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 50f709b25..902516b29 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -139,20 +139,34 @@ void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanose class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { public: - IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") { + explicit IActiveVibrationDeviceList(std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework("IActiveVibrationDeviceList"), applet_resource(applet_resource_) { + // clang-format off static const FunctionInfo functions[] = { - {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"}, + {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"}, }; + // clang-format on + RegisterHandlers(functions); } private: - void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_HID, "(STUBBED) called"); + void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .InitializeVibrationDevice(vibration_device_handle); + + LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", + vibration_device_handle.npad_type, vibration_device_handle.npad_id, + vibration_device_handle.device_index); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + std::shared_ptr<IAppletResource> applet_resource; }; std::shared_ptr<IAppletResource> Hid::GetAppletResource() { @@ -241,7 +255,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {208, nullptr, "GetActualVibrationGcErmCommand"}, {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, - {211, nullptr, "IsVibrationDeviceMounted"}, + {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, @@ -320,142 +334,152 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IAppletResource>(applet_resource); } -void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { +void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto basic_xpad_id{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id, - applet_resource_user_id); + applet_resource->ActivateController(HidController::DebugPad); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::XPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { +void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(0); -} + applet_resource->ActivateController(HidController::Touchscreen); -void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { +void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + applet_resource->ActivateController(HidController::Mouse); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { +void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::Keyboard); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::DebugPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { +void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto flags{rp.Pop<u32>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); - applet_resource->ActivateController(HidController::Touchscreen); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { +void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 basic_xpad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::XPad); + + LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", + parameters.basic_xpad_id, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::Mouse); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { +void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + LOG_DEBUG(Service_HID, "(STUBBED) called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->ActivateController(HidController::Keyboard); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.Push(0); } -void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { +void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto flags{rp.Pop<u32>()}; - LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} + const auto parameters{rp.PopRaw<Parameters>()}; -void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::Gesture); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { - // Should have no effect with how our npad sets up the data +void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); - applet_resource->ActivateController(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -463,11 +487,20 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -475,12 +508,21 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - [[maybe_unused]] const auto enable{rp.Pop<bool>()}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + bool enable_sixaxis_sensor_fusion{}; + INSERT_PADDING_BYTES(3); + Controller_NPad::DeviceHandle sixaxis_handle{}; + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING(Service_HID, + "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " + "device_index={}, applet_resource_user_id={}", + parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -488,14 +530,17 @@ void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto drift_mode{rp.Pop<u32>()}; + const auto sixaxis_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + const auto drift_mode{rp.PopEnum<Controller_NPad::GyroscopeZeroDriftMode>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode{drift_mode}); + .SetGyroscopeZeroDriftMode(drift_mode); - LOG_DEBUG(Service_HID, "called, handle={}, drift_mode={}, applet_resource_user_id={}", handle, + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, " + "applet_resource_user_id={}", + sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index, drift_mode, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -504,29 +549,42 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>( - static_cast<u32>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetGyroscopeZeroDriftMode())); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetGyroscopeZeroDriftMode()); } void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -534,11 +592,18 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -546,15 +611,34 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { .IsSixAxisSensorAtRest()); } +void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::Gesture); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto supported_styleset{rp.Pop<u32>()}; - LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); - applet_resource->GetController<Controller_NPad>(HidController::NPad) .SetSupportedStyleSet({supported_styleset}); + LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -565,21 +649,22 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(controller.GetSupportedStyleSet().raw); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetSupportedStyleSet() + .raw); } void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -588,48 +673,62 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->ActivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->ActivateController(HidController::NPad); } void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->DeactivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->DeactivateController(HidController::NPad); } void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto unknown{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + u64 unknown{}; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id, - applet_resource_user_id, unknown); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", + parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetStyleSetChangedEvent(npad_id)); + .GetStyleSetChangedEvent(parameters.npad_id)); } void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .DisconnectNpad(parameters.npad_id); + + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, + parameters.applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad).DisconnectNPad(npad_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -642,22 +741,41 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetLedPattern(npad_id) - .raw); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLedPattern(npad_id) + .raw); +} + +void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + // Should have no effect with how our npad sets up the data + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->ActivateController(HidController::NPad); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto hold_type{rp.Pop<u64>()}; + const auto hold_type{rp.PopEnum<Controller_NPad::NpadHoldType>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad).SetHoldType(hold_type); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}", applet_resource_user_id, hold_type); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -668,22 +786,26 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - const auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad).GetHoldType()); } void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -692,16 +814,22 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { // TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto npad_joy_device_type{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + u64 npad_joy_device_type{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", - npad_id, applet_resource_user_id, npad_joy_device_type); - - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + parameters.npad_id, parameters.applet_resource_user_id, + parameters.npad_joy_device_type); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -709,14 +837,19 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -728,12 +861,12 @@ void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { const auto npad_id_2{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", npad_id_1, npad_id_2, applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -742,9 +875,9 @@ void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).StartLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.StartLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -754,9 +887,9 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad).StopLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.StopLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -765,13 +898,13 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto mode{rp.Pop<u64>()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, mode={}", applet_resource_user_id, - mode); + const auto activation_mode{rp.PopEnum<Controller_NPad::NpadHandheldActivationMode>()}; applet_resource->GetController<Controller_NPad>(HidController::NPad) - .SetNpadHandheldActivationMode(Controller_NPad::NpadHandheldActivationMode{mode}); + .SetNpadHandheldActivationMode(activation_mode); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}", + applet_resource_user_id, activation_mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -785,23 +918,24 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>( - static_cast<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetNpadHandheldActivationMode())); + rb.PushEnum(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetNpadHandheldActivationMode()); } void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_1{rp.Pop<u32>()}; - const auto npad_2{rp.Pop<u32>()}; + const auto npad_id_1{rp.Pop<u32>()}; + const auto npad_id_2{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}", - applet_resource_user_id, npad_1, npad_2); + const bool res = applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SwapNpadAssignment(npad_id_1, npad_id_2); + + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", + npad_id_1, npad_id_2, applet_resource_user_id); - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; - if (controller.SwapNpadAssignment(npad_1, npad_2)) { + if (res) { rb.Push(RESULT_SUCCESS); } else { LOG_ERROR(Service_HID, "Npads are not connected!"); @@ -811,144 +945,219 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<bool>(controller.IsUnintendedHomeButtonInputProtectionEnabled(npad_id)); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id)); } void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto unintended_home_button_input_protection{rp.Pop<bool>()}; - const auto npad_id{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + bool unintended_home_button_input_protection{}; + INSERT_PADDING_BYTES(3); + u32 npad_id{}; + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetUnintendedHomeButtonInputProtectionEnabled( + parameters.unintended_home_button_input_protection, parameters.npad_id); LOG_WARNING(Service_HID, "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={}," "applet_resource_user_id={}", - npad_id, unintended_home_button_input_protection, applet_resource_user_id); - - auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); - controller.SetUnintendedHomeButtonInputProtectionEnabled( - unintended_home_button_input_protection, npad_id); + parameters.unintended_home_button_input_protection, parameters.npad_id, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { +void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto vibration_device_handle{rp.PopRaw<Controller_NPad::DeviceHandle>()}; + + VibrationDeviceInfo vibration_device_info; + + vibration_device_info.type = VibrationDeviceType::LinearResonantActuator; + + switch (vibration_device_handle.device_index) { + case Controller_NPad::DeviceIndex::Left: + vibration_device_info.position = VibrationDevicePosition::Left; + break; + case Controller_NPad::DeviceIndex::Right: + vibration_device_info.position = VibrationDevicePosition::Right; + break; + case Controller_NPad::DeviceIndex::None: + default: + UNREACHABLE_MSG("DeviceIndex should never be None!"); + vibration_device_info.position = VibrationDevicePosition::None; + break; + } - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}", + vibration_device_info.type, vibration_device_info.position); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(vibration_device_info); +} + +void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + Controller_NPad::VibrationValue vibration_value{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateController(parameters.vibration_device_handle, parameters.vibration_value); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { +void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLastVibration(parameters.vibration_device_handle)); +} + +void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called"); - applet_resource->GetController<Controller_NPad>(HidController::NPad).SetVibrationEnabled(false); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IActiveVibrationDeviceList>(applet_resource); } -void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { +void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller{rp.Pop<u32>()}; - const auto vibration_values{rp.PopRaw<Controller_NPad::Vibration>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + const auto can_vibrate{rp.Pop<bool>()}; - LOG_DEBUG(Service_HID, "called, controller={}, applet_resource_user_id={}", controller, - applet_resource_user_id); + Settings::values.vibration_enabled.SetValue(can_vibrate); + + LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); +} - applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController({controller}, {vibration_values}); +void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(Settings::values.vibration_enabled.GetValue()); } void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - const auto controllers = ctx.ReadBuffer(0); + const auto handles = ctx.ReadBuffer(0); const auto vibrations = ctx.ReadBuffer(1); - std::vector<u32> controller_list(controllers.size() / sizeof(u32)); - std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() / - sizeof(Controller_NPad::Vibration)); + std::vector<Controller_NPad::DeviceHandle> vibration_device_handles( + handles.size() / sizeof(Controller_NPad::DeviceHandle)); + std::vector<Controller_NPad::VibrationValue> vibration_values( + vibrations.size() / sizeof(Controller_NPad::VibrationValue)); - std::memcpy(controller_list.data(), controllers.data(), controllers.size()); - std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); + std::memcpy(vibration_device_handles.data(), handles.data(), handles.size()); + std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size()); applet_resource->GetController<Controller_NPad>(HidController::NPad) - .VibrateController(controller_list, vibration_list); + .VibrateControllers(vibration_device_handles, vibration_values); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { +void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop<u32>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, - applet_resource_user_id); - - IPC::ResponseBuilder rb{ctx, 6}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<Controller_NPad::Vibration>( - applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration()); -} + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetPermitVibrationSession(true); -void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); - rb.Push<u32>(0); } -void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { +void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetPermitVibrationSession(false); + LOG_DEBUG(Service_HID, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IActiveVibrationDeviceList>(); } -void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { +void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto can_vibrate{rp.Pop<bool>()}; - Settings::values.vibration_enabled = can_vibrate; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); + const auto parameters{rp.PopRaw<Parameters>()}; - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(Settings::values.vibration_enabled); + rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .IsVibrationDeviceMounted(parameters.vibration_device_handle)); } void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { @@ -964,11 +1173,19 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -976,11 +1193,19 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop<u32>()}; - const auto applet_resource_user_id{rp.Pop<u64>()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index fd0372b18..c8e4a4b55 100644 --- a/src/core/hle/service/hid/hid.h +++ b/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; }; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 5b0e371fe..55e00dd93 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/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) * diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 0587b9374..aadbc3932 100644 --- a/src/core/settings.cpp +++ b/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() { diff --git a/src/core/settings.h b/src/core/settings.h index 28616a574..476c3fdf3 100644 --- a/src/core/settings.h +++ b/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; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index ebc19e18a..e0908186b 100644 --- a/src/core/telemetry_session.cpp +++ b/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() { diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index b912188b6..d80195c82 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/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; } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index d28dcfad3..f1256c9da 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/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(); diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp index 6bd6f57fc..fe57c13a5 100644 --- a/src/input_common/gcadapter/gc_poller.cpp +++ b/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 diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h index 0527f328f..d1271e3ea 100644 --- a/src/input_common/gcadapter/gc_poller.h +++ b/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 diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index d32fd8b81..e59ad4ff5 100644 --- a/src/input_common/main.cpp +++ b/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; diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h index abb957f04..efe74cf19 100644 --- a/src/input_common/motion_input.h +++ b/src/input_common/motion_input.h @@ -13,7 +13,7 @@ namespace InputCommon { class MotionInput { public: - MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); MotionInput(const MotionInput&) = default; MotionInput& operator=(const MotionInput&) = default; @@ -33,16 +33,17 @@ public: void UpdateRotation(u64 elapsed_time); void UpdateOrientation(u64 elapsed_time); - std::array<Common::Vec3f, 3> GetOrientation() const; - Common::Vec3f GetAcceleration() const; - Common::Vec3f GetGyroscope() const; - Common::Vec3f GetRotations() const; - Common::Quaternion<f32> GetQuaternion() const; - Input::MotionStatus GetMotion() const; - Input::MotionStatus GetRandomMotion(int accel_magnitude, int gyro_magnitude) const; - - bool IsMoving(f32 sensitivity) const; - bool IsCalibrated(f32 sensitivity) const; + [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const; + [[nodiscard]] Common::Vec3f GetAcceleration() const; + [[nodiscard]] Common::Vec3f GetGyroscope() const; + [[nodiscard]] Common::Vec3f GetRotations() const; + [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; + [[nodiscard]] Input::MotionStatus GetMotion() const; + [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude, + int gyro_magnitude) const; + + [[nodiscard]] bool IsMoving(f32 sensitivity) const; + [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; private: void ResetOrientation(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 10883e2d9..8c48bb861 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/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 {}; } diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index b9bb4dc56..08044b00d 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/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; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index b66c05856..557e7a9a0 100644 --- a/src/input_common/settings.cpp +++ b/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", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index f52d28540..75486554b 100644 --- a/src/input_common/settings.h +++ b/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; diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 7039d6fc3..3677e79ca 100644 --- a/src/input_common/udp/client.cpp +++ b/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) { diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index cf5235a79..21410e125 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -17,11 +17,11 @@ #include "video_core/dma_pusher.h" using CacheAddr = std::uintptr_t; -inline CacheAddr ToCacheAddr(const void* host_ptr) { +[[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) { return reinterpret_cast<CacheAddr>(host_ptr); } -inline u8* FromCacheAddr(CacheAddr cache_addr) { +[[nodiscard]] inline u8* FromCacheAddr(CacheAddr cache_addr) { return reinterpret_cast<u8*>(cache_addr); } @@ -149,13 +149,13 @@ public: u32 subchannel{}; u32 method_count{}; - bool IsLastCall() const { - return method_count <= 1; - } - MethodCall(u32 method, u32 argument, u32 subchannel = 0, u32 method_count = 0) : method(method), argument(argument), subchannel(subchannel), method_count(method_count) {} + + [[nodiscard]] bool IsLastCall() const { + return method_count <= 1; + } }; explicit GPU(Core::System& system, bool is_async, bool use_nvdec); @@ -179,10 +179,10 @@ public: virtual void OnCommandListEnd(); /// Request a host GPU memory flush from the CPU. - u64 RequestFlush(VAddr addr, std::size_t size); + [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size); /// Obtains current flush request fence id. - u64 CurrentFlushRequestFence() const { + [[nodiscard]] u64 CurrentFlushRequestFence() const { return current_flush_fence.load(std::memory_order_relaxed); } @@ -190,48 +190,52 @@ public: void TickWork(); /// Returns a reference to the Maxwell3D GPU engine. - Engines::Maxwell3D& Maxwell3D(); + [[nodiscard]] Engines::Maxwell3D& Maxwell3D(); /// Returns a const reference to the Maxwell3D GPU engine. - const Engines::Maxwell3D& Maxwell3D() const; + [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const; /// Returns a reference to the KeplerCompute GPU engine. - Engines::KeplerCompute& KeplerCompute(); + [[nodiscard]] Engines::KeplerCompute& KeplerCompute(); /// Returns a reference to the KeplerCompute GPU engine. - const Engines::KeplerCompute& KeplerCompute() const; + [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const; /// Returns a reference to the GPU memory manager. - Tegra::MemoryManager& MemoryManager(); + [[nodiscard]] Tegra::MemoryManager& MemoryManager(); /// Returns a const reference to the GPU memory manager. - const Tegra::MemoryManager& MemoryManager() const; + [[nodiscard]] const Tegra::MemoryManager& MemoryManager() const; /// Returns a reference to the GPU DMA pusher. - Tegra::DmaPusher& DmaPusher(); + [[nodiscard]] Tegra::DmaPusher& DmaPusher(); /// Returns a const reference to the GPU DMA pusher. - const Tegra::DmaPusher& DmaPusher() const; + [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const; /// Returns a reference to the GPU CDMA pusher. - Tegra::CDmaPusher& CDmaPusher(); + [[nodiscard]] Tegra::CDmaPusher& CDmaPusher(); /// Returns a const reference to the GPU CDMA pusher. - const Tegra::CDmaPusher& CDmaPusher() const; + [[nodiscard]] const Tegra::CDmaPusher& CDmaPusher() const; - VideoCore::RendererBase& Renderer() { + /// Returns a reference to the underlying renderer. + [[nodiscard]] VideoCore::RendererBase& Renderer() { return *renderer; } - const VideoCore::RendererBase& Renderer() const { + /// Returns a const reference to the underlying renderer. + [[nodiscard]] const VideoCore::RendererBase& Renderer() const { return *renderer; } - VideoCore::ShaderNotify& ShaderNotify() { + /// Returns a reference to the shader notifier. + [[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() { return *shader_notify; } - const VideoCore::ShaderNotify& ShaderNotify() const { + /// Returns a const reference to the shader notifier. + [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const { return *shader_notify; } @@ -243,23 +247,23 @@ public: void IncrementSyncPoint(u32 syncpoint_id); - u32 GetSyncpointValue(u32 syncpoint_id) const; + [[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const; void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value); - bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); + [[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); - u64 GetTicks() const; + [[nodiscard]] u64 GetTicks() const; - std::unique_lock<std::mutex> LockSync() { + [[nodiscard]] std::unique_lock<std::mutex> LockSync() { return std::unique_lock{sync_mutex}; } - bool IsAsync() const { + [[nodiscard]] bool IsAsync() const { return is_async; } - bool UseNvdec() const { + [[nodiscard]] bool UseNvdec() const { return use_nvdec; } @@ -273,7 +277,7 @@ public: BitField<0, 1, FenceOperation> op; BitField<8, 24, u32> syncpoint_id; - static CommandHeader Build(FenceOperation op, u32 syncpoint_id) { + [[nodiscard]] static CommandHeader Build(FenceOperation op, u32 syncpoint_id) { FenceAction result{}; result.op.Assign(op); result.syncpoint_id.Assign(syncpoint_id); @@ -291,7 +295,7 @@ public: u32 address_high; u32 address_low; - GPUVAddr SemaphoreAddress() const { + [[nodiscard]] GPUVAddr SemaphoreAddress() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); } @@ -374,7 +378,7 @@ private: u32 methods_pending); /// Determines where the method should be executed. - bool ExecuteMethodOnEngine(u32 method); + [[nodiscard]] bool ExecuteMethodOnEngine(u32 method); protected: Core::System& system; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index b3e0919f8..27ef4c69a 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -32,7 +32,7 @@ using DiskResourceLoadCallback = std::function<void(LoadCallbackStage, std::size class RasterizerInterface { public: - virtual ~RasterizerInterface() {} + virtual ~RasterizerInterface() = default; /// Dispatches a draw invocation virtual void Draw(bool is_indexed, bool is_instanced) = 0; @@ -90,15 +90,16 @@ public: virtual void TickFrame() = 0; /// Attempt to use a faster method to perform a surface copy - virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, - const Tegra::Engines::Fermi2D::Regs::Surface& dst, - const Tegra::Engines::Fermi2D::Config& copy_config) { + [[nodiscard]] virtual bool AccelerateSurfaceCopy( + const Tegra::Engines::Fermi2D::Regs::Surface& src, + const Tegra::Engines::Fermi2D::Regs::Surface& dst, + const Tegra::Engines::Fermi2D::Config& copy_config) { return false; } /// Attempt to use a faster method to display the framebuffer to screen - virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, - u32 pixel_stride) { + [[nodiscard]] virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& config, + VAddr framebuffer_addr, u32 pixel_stride) { return false; } @@ -110,12 +111,12 @@ public: const DiskResourceLoadCallback& callback) {} /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. - GuestDriverProfile& AccessGuestDriverProfile() { + [[nodiscard]] GuestDriverProfile& AccessGuestDriverProfile() { return guest_driver_profile; } /// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver. - const GuestDriverProfile& AccessGuestDriverProfile() const { + [[nodiscard]] const GuestDriverProfile& AccessGuestDriverProfile() const { return guest_driver_profile; } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 5c650808b..51dde8eb5 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -38,7 +38,7 @@ public: virtual ~RendererBase(); /// Initialize the renderer - virtual bool Init() = 0; + [[nodiscard]] virtual bool Init() = 0; /// Shutdown the renderer virtual void ShutDown() = 0; @@ -49,43 +49,43 @@ public: // Getter/setter functions: // ------------------------ - f32 GetCurrentFPS() const { + [[nodiscard]] f32 GetCurrentFPS() const { return m_current_fps; } - int GetCurrentFrame() const { + [[nodiscard]] int GetCurrentFrame() const { return m_current_frame; } - RasterizerInterface& Rasterizer() { + [[nodiscard]] RasterizerInterface& Rasterizer() { return *rasterizer; } - const RasterizerInterface& Rasterizer() const { + [[nodiscard]] const RasterizerInterface& Rasterizer() const { return *rasterizer; } - Core::Frontend::GraphicsContext& Context() { + [[nodiscard]] Core::Frontend::GraphicsContext& Context() { return *context; } - const Core::Frontend::GraphicsContext& Context() const { + [[nodiscard]] const Core::Frontend::GraphicsContext& Context() const { return *context; } - Core::Frontend::EmuWindow& GetRenderWindow() { + [[nodiscard]] Core::Frontend::EmuWindow& GetRenderWindow() { return render_window; } - const Core::Frontend::EmuWindow& GetRenderWindow() const { + [[nodiscard]] const Core::Frontend::EmuWindow& GetRenderWindow() const { return render_window; } - RendererSettings& Settings() { + [[nodiscard]] RendererSettings& Settings() { return renderer_settings; } - const RendererSettings& Settings() const { + [[nodiscard]] const RendererSettings& Settings() const { return renderer_settings; } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 8abb74d56..b16b54032 100644 --- a/src/yuzu/CMakeLists.txt +++ b/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 diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index f122ba39d..1b320630c 100644 --- a/src/yuzu/aboutdialog.ui +++ b/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> diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp index c6fa3e4f6..8ecfec770 100644 --- a/src/yuzu/applets/controller.cpp +++ b/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(); diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 729ecc831..4344e1dd0 100644 --- a/src/yuzu/applets/controller.h +++ b/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}; diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui index c4108a979..c8cb6bcf3 100644 --- a/src/yuzu/applets/controller.ui +++ b/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> diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 408eac2b7..d62b0efc2 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -10,6 +10,7 @@ #include <QMessageBox> #include <QPainter> #include <QScreen> +#include <QString> #include <QStringList> #include <QWindow> @@ -381,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; } @@ -397,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; } @@ -410,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; } @@ -603,19 +614,33 @@ bool GRenderWindow::LoadOpenGL() { auto context = CreateSharedContext(); auto scope = context->Acquire(); if (!gladLoadGL()) { - QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"), - tr("Your GPU may not support OpenGL 4.3, or you do not have the " - "latest graphics driver.")); + QMessageBox::warning( + this, tr("Error while initializing OpenGL!"), + tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver.")); + return false; + } + + const QString renderer = + QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER))); + + if (!GLAD_GL_VERSION_4_3) { + LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString()); + QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"), + tr("Your GPU may not support OpenGL 4.3, or you do not have the " + "latest graphics driver.<br><br>GL Renderer:<br>%1") + .arg(renderer)); return false; } QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions(); if (!unsupported_gl_extensions.empty()) { - QMessageBox::critical( + QMessageBox::warning( this, tr("Error while initializing OpenGL!"), tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " - "have the latest graphics driver.<br><br>Unsupported extensions:<br>") + - unsupported_gl_extensions.join(QStringLiteral("<br>"))); + "have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported " + "extensions:<br>%2") + .arg(renderer) + .arg(unsupported_gl_extensions.join(QStringLiteral("<br>")))); return false; } return true; @@ -645,8 +670,13 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { if (!GLAD_GL_ARB_depth_buffer_float) unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float")); - for (const QString& ext : unsupported_ext) - LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + if (!unsupported_ext.empty()) { + LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", + glGetString(GL_RENDERER)); + } + for (const QString& ext : unsupported_ext) { + LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); + } return unsupported_ext; } diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 1ce62e4a6..6fa842cd5 100644 --- a/src/yuzu/configuration/config.cpp +++ b/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; +} diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 5d8e45d78..8a600e19d 100644 --- a/src/yuzu/configuration/config.h +++ b/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; }; diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index fcf42cdcb..f92c3aff3 100644 --- a/src/yuzu/configuration/configure.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp index 0097c9a29..a878ef9c6 100644 --- a/src/yuzu/configuration/configure_debug_controller.cpp +++ b/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); diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h index 34dcf705f..b4f53fad5 100644 --- a/src/yuzu/configuration/configure_debug_controller.h +++ b/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(); diff --git a/src/yuzu/configuration/configure_debug_controller.ui b/src/yuzu/configuration/configure_debug_controller.ui index a95ed50ff..7b7e6582c 100644 --- a/src/yuzu/configuration/configure_debug_controller.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 2725fcb2b..d9009091b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/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(); } } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 0e8b2fd4e..f4eb0d78b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/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; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 136955224..2707025e7 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 81f9dc16c..abaf03630 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/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())); } } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 50bb87768..3083d55c1 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/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(); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 5958435fc..a880a7c68 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/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/> diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp deleted file mode 100644 index 1866003c2..000000000 --- a/src/yuzu/configuration/configure_input_dialog.cpp +++ /dev/null @@ -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); -} diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h deleted file mode 100644 index d1bd865f9..000000000 --- a/src/yuzu/configuration/configure_input_dialog.h +++ /dev/null @@ -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; -}; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index f58ca29d7..72640f5e7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/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); } diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index c19aefffa..23cf6f958 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/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 diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index e03461d9d..1e78b4c10 100644 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_input_profile_dialog.cpp b/src/yuzu/configuration/configure_input_profile_dialog.cpp new file mode 100644 index 000000000..1f5cfa75b --- /dev/null +++ b/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); +} diff --git a/src/yuzu/configuration/configure_input_profile_dialog.h b/src/yuzu/configuration/configure_input_profile_dialog.h new file mode 100644 index 000000000..e6386bdbb --- /dev/null +++ b/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; +}; diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_profile_dialog.ui index b92ddb200..726cf6905 100644 --- a/src/yuzu/configuration/configure_input_dialog.ui +++ b/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,11 +30,25 @@ <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"> <set>QDialogButtonBox::Ok</set> @@ -50,7 +64,7 @@ <connection> <sender>buttonBox</sender> <signal>accepted()</signal> - <receiver>ConfigureInputDialog</receiver> + <receiver>ConfigureInputProfileDialog</receiver> <slot>accept()</slot> </connection> </connections> diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui index 602cf8cd8..5b78c5a4b 100644 --- a/src/yuzu/configuration/configure_motion_touch.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui index 74552fdbd..5b99e1c37 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 002db3f93..81464dd37 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/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); diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui index d2057c4ab..25975b3b9 100644 --- a/src/yuzu/configuration/configure_per_game.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui index f581e27e0..757219d54 100644 --- a/src/yuzu/configuration/configure_touch_from_button.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui index 1171c2dd1..30ceccddb 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.ui +++ b/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> diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp new file mode 100644 index 000000000..7dcb2c5b9 --- /dev/null +++ b/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); +} diff --git a/src/yuzu/configuration/configure_vibration.h b/src/yuzu/configuration/configure_vibration.h new file mode 100644 index 000000000..07411a86f --- /dev/null +++ b/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; +}; diff --git a/src/yuzu/configuration/configure_vibration.ui b/src/yuzu/configuration/configure_vibration.ui new file mode 100644 index 000000000..efdf317a9 --- /dev/null +++ b/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> diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp new file mode 100644 index 000000000..e87aededb --- /dev/null +++ b/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(); +} diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h new file mode 100644 index 000000000..cb41fd9be --- /dev/null +++ b/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; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 18e68e590..9dabd8889 100644 --- a/src/yuzu/main.cpp +++ b/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() || diff --git a/src/yuzu/main.h b/src/yuzu/main.h index afcfa68a9..b380a66f3 100644 --- a/src/yuzu/main.h +++ b/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(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 334038ef9..e1adbbf2b 100644 --- a/src/yuzu_cmd/config.cpp +++ b/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>( diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 796e27df4..bcbbcd4ca 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/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) diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp index bc273fb51..b6cdc7c1c 100644 --- a/src/yuzu_tester/config.cpp +++ b/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 = |