diff options
Diffstat (limited to 'src/yuzu')
74 files changed, 4292 insertions, 1842 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index cc0291b15..e1bab2112 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 @@ -136,6 +141,8 @@ add_executable(yuzu util/limitable_input_dialog.h util/sequence_dialog/sequence_dialog.cpp util/sequence_dialog/sequence_dialog.h + util/url_request_interceptor.cpp + util/url_request_interceptor.h util/util.cpp util/util.h compatdb.cpp @@ -212,7 +219,8 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core) target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -if (ENABLE_VULKAN AND NOT WIN32) +target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) +if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) endif() @@ -264,17 +272,12 @@ endif() if (MSVC) include(CopyYuzuQt5Deps) include(CopyYuzuSDLDeps) - include(CopyYuzuUnicornDeps) + include(CopyYuzuFFmpegDeps) copy_yuzu_Qt5_deps(yuzu) copy_yuzu_SDL_deps(yuzu) - copy_yuzu_unicorn_deps(yuzu) + copy_yuzu_FFmpeg_deps(yuzu) endif() if (NOT APPLE) target_compile_definitions(yuzu PRIVATE HAS_OPENGL) endif() - -if (ENABLE_VULKAN) - target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include) - target_compile_definitions(yuzu PRIVATE HAS_VULKAN) -endif() 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 9d45f2a01..c680fd2c2 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,20 +14,25 @@ #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::array<std::array<bool, 4>, 8> led_patterns = {{ - {1, 0, 0, 0}, - {1, 1, 0, 0}, - {1, 1, 1, 0}, - {1, 1, 1, 1}, - {1, 0, 0, 1}, - {1, 0, 1, 0}, - {1, 0, 1, 1}, - {0, 1, 1, 0}, +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}, + {true, true, true, false}, + {true, true, true, true}, + {true, false, false, true}, + {true, false, true, false}, + {true, false, true, true}, + {false, true, true, false}, }}; void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index, @@ -66,47 +72,14 @@ bool IsControllerCompatible(Settings::ControllerType controller_type, } } -/// Maps the controller type combobox index to Controller Type enum -constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { - switch (index) { - case 0: - default: - return Settings::ControllerType::ProController; - case 1: - return Settings::ControllerType::DualJoyconDetached; - case 2: - return Settings::ControllerType::LeftJoycon; - case 3: - return Settings::ControllerType::RightJoycon; - case 4: - return Settings::ControllerType::Handheld; - } -} - -/// Maps the Controller Type enum to controller type combobox index -constexpr int GetIndexFromControllerType(Settings::ControllerType type) { - switch (type) { - case Settings::ControllerType::ProController: - default: - return 0; - case Settings::ControllerType::DualJoyconDetached: - return 1; - case Settings::ControllerType::LeftJoycon: - return 2; - case Settings::ControllerType::RightJoycon: - return 3; - case Settings::ControllerType::Handheld: - return 4; - } -} - } // namespace 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 = { @@ -177,6 +150,11 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( // This avoids unintentionally changing the states of elements while loading them in. SetSupportedControllers(); DisableUnsupportedPlayers(); + + for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) { + SetEmulatedControllers(player_index); + } + LoadConfiguration(); for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { @@ -216,19 +194,29 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( if (i == 0) { connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), - [this](int index) { - UpdateDockedState(GetControllerTypeFromIndex(index) == + [this, i](int index) { + UpdateDockedState(GetControllerTypeFromIndex(index, i) == Settings::ControllerType::Handheld); }); } } + 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 +224,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)); + emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType( + Settings::values.players.GetValue()[index].controller_type, index)); } - 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 +297,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. @@ -313,7 +309,7 @@ void QtControllerSelectorDialog::CheckIfParametersMet() { } const auto compatible = IsControllerCompatible( - GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()), + GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index), parameters); // If any controller is found to be incompatible, return false early. @@ -326,18 +322,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"))) { @@ -402,6 +393,63 @@ void QtControllerSelectorDialog::SetSupportedControllers() { } } +void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) { + auto& pairs = index_controller_type_pairs[player_index]; + + pairs.clear(); + emulated_controllers[player_index]->clear(); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::ProController); + emulated_controllers[player_index]->addItem(tr("Pro Controller")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::DualJoyconDetached); + emulated_controllers[player_index]->addItem(tr("Dual Joycons")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::LeftJoycon); + emulated_controllers[player_index]->addItem(tr("Left Joycon")); + + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::RightJoycon); + emulated_controllers[player_index]->addItem(tr("Right Joycon")); + + if (player_index == 0) { + pairs.emplace_back(emulated_controllers[player_index]->count(), + Settings::ControllerType::Handheld); + emulated_controllers[player_index]->addItem(tr("Handheld")); + } +} + +Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( + int index, std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [index](const auto& pair) { return pair.first == index; }); + + if (it == pairs.end()) { + return Settings::ControllerType::ProController; + } + + return it->second; +} + +int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type, + std::size_t player_index) const { + const auto& pairs = index_controller_type_pairs[player_index]; + + const auto it = std::find_if(pairs.begin(), pairs.end(), + [type](const auto& pair) { return pair.second == type; }); + + if (it == pairs.end()) { + return 0; + } + + return it->first; +} + void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) { if (!player_groupboxes[player_index]->isChecked()) { connected_controller_icons[player_index]->setStyleSheet(QString{}); @@ -410,7 +458,8 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) } const QString stylesheet = [this, player_index] { - switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) { + switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index)) { case Settings::ControllerType::ProController: return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); case Settings::ControllerType::DualJoyconDetached: @@ -426,7 +475,13 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) } }(); - const QString theme = [this] { + if (stylesheet.isEmpty()) { + connected_controller_icons[player_index]->setStyleSheet(QString{}); + player_labels[player_index]->show(); + return; + } + + const QString theme = [] { if (QIcon::themeName().contains(QStringLiteral("dark"))) { return QStringLiteral("_dark"); } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { @@ -441,38 +496,54 @@ 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 = - GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()); - player.connected = player_groupboxes[player_index]->isChecked(); + const auto controller_type = GetControllerTypeFromIndex( + emulated_controllers[player_index]->currentIndex(), player_index); + 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(60ms); + + UpdateController(controller_type, player_index, player_connected); } void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) { if (!player_groupboxes[player_index]->isChecked() || - GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) == - Settings::ControllerType::Handheld) { + GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), + player_index) == Settings::ControllerType::Handheld) { led_patterns_boxes[player_index][0]->setChecked(false); led_patterns_boxes[player_index][1]->setChecked(false); led_patterns_boxes[player_index][2]->setChecked(false); @@ -520,8 +591,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 +635,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(); @@ -589,13 +660,13 @@ QtControllerSelector::QtControllerSelector(GMainWindow& parent) { QtControllerSelector::~QtControllerSelector() = default; void QtControllerSelector::ReconfigureControllers( - std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const { - this->callback = std::move(callback); + std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const { + callback = std::move(callback_); emit MainWindowReconfigureControllers(parameters); } void QtControllerSelector::MainWindowReconfigureFinished() { // Acquire the HLE mutex - std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + std::lock_guard lock(HLE::g_hle_lock); callback(); } diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h index 2d6d588c6..3518eed56 100644 --- a/src/yuzu/applets/controller.h +++ b/src/yuzu/applets/controller.h @@ -16,10 +16,16 @@ class QDialogButtonBox; class QGroupBox; class QLabel; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } +namespace Settings { +enum class ControllerType; +} + namespace Ui { class QtControllerSelectorDialog; } @@ -33,6 +39,8 @@ public: InputCommon::InputSubsystem* input_subsystem_); ~QtControllerSelectorDialog() override; + int exec() override; + private: // Applies the current configuration. void ApplyConfiguration(); @@ -40,16 +48,28 @@ 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(); + + // Initializes the "Create Input Profile" Dialog. + void CallConfigureInputProfileDialog(); - // Checks the current configuration against the given parameters and - // sets the value of parameters_met. - void CheckIfParametersMet(); + // 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(); + // Sets the emulated controllers per player. + void SetEmulatedControllers(std::size_t player_index); + + // Gets the Controller Type for a given controller combobox index per player. + Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const; + + // Gets the controller combobox index for a given Controller Type per player. + int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const; + // Updates the controller icons per player. void UpdateControllerIcon(std::size_t player_index); @@ -78,6 +98,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}; @@ -105,6 +127,10 @@ private: // Comboboxes with a list of emulated controllers per player. std::array<QComboBox*, NUM_PLAYERS> emulated_controllers; + /// Pairs of emulated controller index and Controller Type enum per player. + std::array<std::vector<std::pair<int, Settings::ControllerType>>, NUM_PLAYERS> + index_controller_type_pairs; + // Labels representing the number of connected controllers // above the "Connected Controllers" checkboxes. std::array<QLabel*, NUM_PLAYERS> connected_controller_labels; @@ -120,11 +146,13 @@ public: explicit QtControllerSelector(GMainWindow& parent); ~QtControllerSelector() override; - void ReconfigureControllers(std::function<void()> callback, - Core::Frontend::ControllerParameters parameters) const override; + void ReconfigureControllers( + std::function<void()> callback_, + const Core::Frontend::ControllerParameters& parameters) const override; signals: - void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const; + void MainWindowReconfigureControllers( + const Core::Frontend::ControllerParameters& parameters) const; private: void MainWindowReconfigureFinished(); 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/applets/error.cpp b/src/yuzu/applets/error.cpp index 08ed57355..8ee03ddb3 100644 --- a/src/yuzu/applets/error.cpp +++ b/src/yuzu/applets/error.cpp @@ -17,9 +17,9 @@ QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) { QtErrorDisplay::~QtErrorDisplay() = default; void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occured.\nPlease try again or contact the developer of the " + tr("An error has occurred.\nPlease try again or contact the developer of the " "software.\n\nError Code: %1-%2 (0x%3)") .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) @@ -28,11 +28,11 @@ void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count()); emit MainWindowDisplayError( - tr("An error occured on %1 at %2.\nPlease try again or contact the " + tr("An error occurred on %1 at %2.\nPlease try again or contact the " "developer of the software.\n\nError Code: %3-%4 (0x%5)") .arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy"))) .arg(date_time.toString(QStringLiteral("h:mm:ss A"))) @@ -44,9 +44,9 @@ void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::secon void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text, std::function<void()> finished) const { - this->callback = std::move(finished); + callback = std::move(finished); emit MainWindowDisplayError( - tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") + tr("An error has occurred.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5") .arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0')) .arg(error.description, 4, 10, QChar::fromLatin1('0')) .arg(error.raw, 8, 16, QChar::fromLatin1('0')) diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp index dca8835ed..4bf2bfd40 100644 --- a/src/yuzu/applets/profile_select.cpp +++ b/src/yuzu/applets/profile_select.cpp @@ -114,6 +114,15 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; +int QtProfileSelectionDialog::exec() { + // Skip profile selection when there's only one. + if (profile_manager->GetUserCount() == 1) { + user_index = 0; + return QDialog::Accepted; + } + return QDialog::exec(); +} + void QtProfileSelectionDialog::accept() { QDialog::accept(); } @@ -141,8 +150,8 @@ QtProfileSelector::QtProfileSelector(GMainWindow& parent) { QtProfileSelector::~QtProfileSelector() = default; void QtProfileSelector::SelectProfile( - std::function<void(std::optional<Common::UUID>)> callback) const { - this->callback = std::move(callback); + std::function<void(std::optional<Common::UUID>)> callback_) const { + callback = std::move(callback_); emit MainWindowSelectProfile(); } diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h index cee886a77..4e9037488 100644 --- a/src/yuzu/applets/profile_select.h +++ b/src/yuzu/applets/profile_select.h @@ -27,6 +27,7 @@ public: explicit QtProfileSelectionDialog(QWidget* parent); ~QtProfileSelectionDialog() override; + int exec() override; void accept() override; void reject() override; @@ -59,7 +60,7 @@ public: explicit QtProfileSelector(GMainWindow& parent); ~QtProfileSelector() override; - void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback) const override; + void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override; signals: void MainWindowSelectProfile() const; diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index af36f07c6..ab8cfd8ee 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -135,8 +135,8 @@ void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16st } void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const { - this->finished_check = std::move(finished_check); + std::function<void()> finished_check_) const { + finished_check = std::move(finished_check_); emit MainWindowTextCheckDialog(error_message); } diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 44bcece75..9e1094cce 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -61,7 +61,7 @@ public: void RequestText(std::function<void(std::optional<std::u16string>)> out, Core::Frontend::SoftwareKeyboardParameters parameters) const override; void SendTextCheckDialog(std::u16string error_message, - std::function<void()> finished_check) const override; + std::function<void()> finished_check_) const override; signals: void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index 33f1c385d..e482ba029 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -1,115 +1,414 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <mutex> - +#ifdef YUZU_USE_QT_WEB_ENGINE #include <QKeyEvent> -#include "core/hle/lock.h" +#include <QWebEngineProfile> +#include <QWebEngineScript> +#include <QWebEngineScriptCollection> +#include <QWebEngineSettings> +#include <QWebEngineUrlScheme> +#endif + +#include "common/file_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" #include "yuzu/applets/web_browser.h" +#include "yuzu/applets/web_browser_scripts.h" #include "yuzu/main.h" +#include "yuzu/util/url_request_interceptor.h" #ifdef YUZU_USE_QT_WEB_ENGINE -constexpr char NX_SHIM_INJECT_SCRIPT[] = R"( - window.nx = {}; - window.nx.playReport = {}; - window.nx.playReport.setCounterSetIdentifier = function () { - console.log("nx.playReport.setCounterSetIdentifier called - unimplemented"); - }; +namespace { - window.nx.playReport.incrementCounter = function () { - console.log("nx.playReport.incrementCounter called - unimplemented"); - }; +constexpr int HIDButtonToKey(HIDButton button) { + switch (button) { + case HIDButton::DLeft: + case HIDButton::LStickLeft: + return Qt::Key_Left; + case HIDButton::DUp: + case HIDButton::LStickUp: + return Qt::Key_Up; + case HIDButton::DRight: + case HIDButton::LStickRight: + return Qt::Key_Right; + case HIDButton::DDown: + case HIDButton::LStickDown: + return Qt::Key_Down; + default: + return 0; + } +} + +} // Anonymous namespace + +QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_) + : QWebEngineView(parent), input_subsystem{input_subsystem_}, + url_interceptor(std::make_unique<UrlRequestInterceptor>()), + input_interpreter(std::make_unique<InputInterpreter>(system)), + default_profile{QWebEngineProfile::defaultProfile()}, + global_settings{QWebEngineSettings::globalSettings()} { + QWebEngineScript gamepad; + QWebEngineScript window_nx; + + gamepad.setName(QStringLiteral("gamepad_script.js")); + window_nx.setName(QStringLiteral("window_nx_script.js")); + + gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT)); + window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT)); + + gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation); + window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation); + + gamepad.setWorldId(QWebEngineScript::MainWorld); + window_nx.setWorldId(QWebEngineScript::MainWorld); + + gamepad.setRunsOnSubFrames(true); + window_nx.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(gamepad); + default_profile->scripts()->insert(window_nx); + + default_profile->setRequestInterceptor(url_interceptor.get()); + + global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true); + global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true); + global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true); + global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false); + + global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto")); + + connect( + page(), &QWebEnginePage::windowCloseRequested, page(), + [this] { + if (page()->url() == url_interceptor->GetRequestedURL()) { + SetFinished(true); + SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed); + } + }, + Qt::QueuedConnection); +} + +QtNXWebEngineView::~QtNXWebEngineView() { + SetFinished(true); + StopInputThread(); +} + +void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url, + std::string_view additional_args) { + is_local = true; + + LoadExtractedFonts(); + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() + + QString::fromStdString(std::string(additional_args)))); +} + +void QtNXWebEngineView::LoadExternalWebPage(std::string_view main_url, + std::string_view additional_args) { + is_local = false; + + SetUserAgent(UserAgent::WebApplet); + SetFinished(false); + SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed); + SetLastURL("http://localhost/"); + StartInputThread(); + + load(QUrl(QString::fromStdString(std::string(main_url)) + + QString::fromStdString(std::string(additional_args)))); +} + +void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) { + const QString user_agent_str = [user_agent] { + switch (user_agent) { + case UserAgent::WebApplet: + default: + return QStringLiteral("WebApplet"); + case UserAgent::ShopN: + return QStringLiteral("ShopN"); + case UserAgent::LoginApplet: + return QStringLiteral("LoginApplet"); + case UserAgent::ShareApplet: + return QStringLiteral("ShareApplet"); + case UserAgent::LobbyApplet: + return QStringLiteral("LobbyApplet"); + case UserAgent::WifiWebAuthApplet: + return QStringLiteral("WifiWebAuthApplet"); + } + }(); + + QWebEngineProfile::defaultProfile()->setHttpUserAgent( + QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 " + "(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389") + .arg(user_agent_str)); +} + +bool QtNXWebEngineView::IsFinished() const { + return finished; +} + +void QtNXWebEngineView::SetFinished(bool finished_) { + finished = finished_; +} + +Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const { + return exit_reason; +} + +void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) { + exit_reason = exit_reason_; +} + +const std::string& QtNXWebEngineView::GetLastURL() const { + return last_url; +} + +void QtNXWebEngineView::SetLastURL(std::string last_url_) { + last_url = std::move(last_url_); +} + +QString QtNXWebEngineView::GetCurrentURL() const { + return url_interceptor->GetRequestedURL().toString(); +} + +void QtNXWebEngineView::hide() { + SetFinished(true); + StopInputThread(); - window.nx.footer = {}; - window.nx.footer.unsetAssign = function () { - console.log("nx.footer.unsetAssign called - unimplemented"); + QWidget::hide(); +} + +void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) { + if (is_local) { + input_subsystem->GetKeyboard()->ReleaseKey(event->key()); + } +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + page()->runJavaScript( + QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)), + [&](const QVariant& variant) { + if (variant.toBool()) { + switch (button) { + case HIDButton::A: + SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>(); + break; + case HIDButton::B: + SendKeyPressEvent(Qt::Key_B); + break; + case HIDButton::X: + SendKeyPressEvent(Qt::Key_X); + break; + case HIDButton::Y: + SendKeyPressEvent(Qt::Key_Y); + break; + default: + break; + } + } + }); + + page()->runJavaScript( + QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }") + .arg(static_cast<u8>(button))); + } }; - var yuzu_key_callbacks = []; - window.nx.footer.setAssign = function(key, discard1, func, discard2) { - switch (key) { - case 'A': - yuzu_key_callbacks[0] = func; - break; - case 'B': - yuzu_key_callbacks[1] = func; - break; - case 'X': - yuzu_key_callbacks[2] = func; - break; - case 'Y': - yuzu_key_callbacks[3] = func; - break; - case 'L': - yuzu_key_callbacks[6] = func; - break; - case 'R': - yuzu_key_callbacks[7] = func; - break; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); } }; - var applet_done = false; - window.nx.endApplet = function() { - applet_done = true; + (f(T), ...); +} + +template <HIDButton... T> +void QtNXWebEngineView::HandleWindowKeyButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + SendKeyPressEvent(HIDButtonToKey(button)); + } }; - window.onkeypress = function(e) { if (e.keyCode === 13) { applet_done = true; } }; -)"; + (f(T), ...); +} + +void QtNXWebEngineView::SendKeyPressEvent(int key) { + if (key == 0) { + return; + } + + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier)); + QCoreApplication::postEvent(focusProxy(), + new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier)); +} + +void QtNXWebEngineView::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + input_thread = std::thread(&QtNXWebEngineView::InputThread, this); +} + +void QtNXWebEngineView::StopInputThread() { + if (is_local) { + QWidget::releaseKeyboard(); + } -QString GetNXShimInjectionScript() { - return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); + input_thread_running = false; + if (input_thread.joinable()) { + input_thread.join(); + } } -NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} +void QtNXWebEngineView::InputThread() { + // Wait for 1 second before allowing any inputs to be processed. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (is_local) { + QWidget::grabKeyboard(); + } + + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y, + HIDButton::L, HIDButton::R>(); + + HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, + HIDButton::DDown, HIDButton::LStickLeft, + HIDButton::LStickUp, HIDButton::LStickRight, + HIDButton::LStickDown>(); -void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { - parent()->event(event); + HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight, + HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp, + HIDButton::LStickRight, HIDButton::LStickDown>(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } } -void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { - parent()->event(event); +void QtNXWebEngineView::LoadExtractedFonts() { + QWebEngineScript nx_font_css; + QWebEngineScript load_nx_font; + + const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)))); + + nx_font_css.setName(QStringLiteral("nx_font_css.js")); + load_nx_font.setName(QStringLiteral("load_nx_font.js")); + + nx_font_css.setSourceCode( + QString::fromStdString(NX_FONT_CSS) + .arg(fonts_dir + QStringLiteral("/FontStandard.ttf")) + .arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf")) + .arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf")) + .arg(fonts_dir + QStringLiteral("/FontKorean.ttf")) + .arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf")) + .arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf"))); + load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT)); + + nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady); + load_nx_font.setInjectionPoint(QWebEngineScript::Deferred); + + nx_font_css.setWorldId(QWebEngineScript::MainWorld); + load_nx_font.setWorldId(QWebEngineScript::MainWorld); + + nx_font_css.setRunsOnSubFrames(true); + load_nx_font.setRunsOnSubFrames(true); + + default_profile->scripts()->insert(nx_font_css); + default_profile->scripts()->insert(load_nx_font); + + connect( + url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(), + [this] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT)); + }, + Qt::QueuedConnection); } #endif QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { - connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage, - Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this, - &QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection); - connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this, - &QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection); + connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window, + &GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this, + &QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::WebBrowserClosed, this, + &QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection); } QtWebBrowser::~QtWebBrowser() = default; -void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { - this->unpack_romfs_callback = std::move(unpack_romfs_callback); - this->finished_callback = std::move(finished_callback); +void QtWebBrowser::OpenLocalWebPage( + std::string_view local_url, std::function<void()> extract_romfs_callback_, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const { + extract_romfs_callback = std::move(extract_romfs_callback_); + callback = std::move(callback_); + + const auto index = local_url.find('?'); + + if (index == std::string::npos) { + emit MainWindowOpenWebPage(local_url, "", true); + } else { + emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true); + } +} + +void QtWebBrowser::OpenExternalWebPage( + std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const { + callback = std::move(callback_); + + const auto index = external_url.find('?'); - const auto index = url.find('?'); if (index == std::string::npos) { - emit MainWindowOpenPage(url, ""); + emit MainWindowOpenWebPage(external_url, "", false); } else { - const auto front = url.substr(0, index); - const auto back = url.substr(index); - emit MainWindowOpenPage(front, back); + emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), + false); } } -void QtWebBrowser::MainWindowUnpackRomFS() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - unpack_romfs_callback(); +void QtWebBrowser::MainWindowExtractOfflineRomFS() { + extract_romfs_callback(); } -void QtWebBrowser::MainWindowFinishedBrowsing() { - // Acquire the HLE mutex - std::lock_guard lock{HLE::g_hle_lock}; - finished_callback(); +void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url) { + callback(exit_reason, last_url); } diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index b38437e46..47f960d69 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -1,10 +1,13 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include <functional> +#include <atomic> +#include <memory> +#include <thread> + #include <QObject> #ifdef YUZU_USE_QT_WEB_ENGINE @@ -13,19 +16,172 @@ #include "core/frontend/applets/web_browser.h" +enum class HIDButton : u8; + class GMainWindow; +class InputInterpreter; +class UrlRequestInterceptor; + +namespace Core { +class System; +} + +namespace InputCommon { +class InputSubsystem; +} #ifdef YUZU_USE_QT_WEB_ENGINE -QString GetNXShimInjectionScript(); +enum class UserAgent { + WebApplet, + ShopN, + LoginApplet, + ShareApplet, + LobbyApplet, + WifiWebAuthApplet, +}; + +class QWebEngineProfile; +class QWebEngineSettings; + +class QtNXWebEngineView : public QWebEngineView { + Q_OBJECT -class NXInputWebEngineView : public QWebEngineView { public: - explicit NXInputWebEngineView(QWidget* parent = nullptr); + explicit QtNXWebEngineView(QWidget* parent, Core::System& system, + InputCommon::InputSubsystem* input_subsystem_); + ~QtNXWebEngineView() override; + + /** + * Loads a HTML document that exists locally. Cannot be used to load external websites. + * + * @param main_url The url to the file. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args); + + /** + * Loads an external website. Cannot be used to load local urls. + * + * @param main_url The url to the website. + * @param additional_args Additional arguments appended to the main url. + */ + void LoadExternalWebPage(std::string_view main_url, std::string_view additional_args); + + /** + * Sets the background color of the web page. + * + * @param color The color to set. + */ + void SetBackgroundColor(QColor color); + + /** + * Sets the user agent of the web browser. + * + * @param user_agent The user agent enum. + */ + void SetUserAgent(UserAgent user_agent); + + [[nodiscard]] bool IsFinished() const; + void SetFinished(bool finished_); + + [[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const; + void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_); + + [[nodiscard]] const std::string& GetLastURL() const; + void SetLastURL(std::string last_url_); + + /** + * This gets the current URL that has been requested by the webpage. + * This only applies to the main frame. Sub frames and other resources are ignored. + * + * @return Currently requested URL + */ + [[nodiscard]] QString GetCurrentURL() const; + +public slots: + void hide(); protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + +private: + /** + * Handles button presses to execute functions assigned in yuzu_key_callbacks. + * yuzu_key_callbacks contains specialized functions for the buttons in the window footer + * that can be overriden by games to achieve desired functionality. + * + * @tparam HIDButton The list of buttons contained in yuzu_key_callbacks + */ + template <HIDButton... T> + void HandleWindowFooterButtonPressedOnce(); + + /** + * Handles button presses and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template <HIDButton... T> + void HandleWindowKeyButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * This should only be used to convert D-Pad or Analog Stick input into arrow keys. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template <HIDButton... T> + void HandleWindowKeyButtonHold(); + + /** + * Sends a key press event to QWebEngineView. + * + * @param key Qt key code. + */ + void SendKeyPressEvent(int key); + + /** + * Sends multiple key press events to QWebEngineView. + * + * @tparam int Qt key code. + */ + template <int... T> + void SendMultipleKeyPressEvents() { + (SendKeyPressEvent(T), ...); + } + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + /// Loads the extracted fonts using JavaScript. + void LoadExtractedFonts(); + + InputCommon::InputSubsystem* input_subsystem; + + std::unique_ptr<UrlRequestInterceptor> url_interceptor; + + std::unique_ptr<InputInterpreter> input_interpreter; + + std::thread input_thread; + + std::atomic<bool> input_thread_running{}; + + std::atomic<bool> finished{}; + + Service::AM::Applets::WebExitReason exit_reason{ + Service::AM::Applets::WebExitReason::EndButtonPressed}; + + std::string last_url{"http://localhost/"}; + + bool is_local{}; + + QWebEngineProfile* default_profile; + QWebEngineSettings* global_settings; }; #endif @@ -34,19 +190,28 @@ class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserAppl Q_OBJECT public: - explicit QtWebBrowser(GMainWindow& main_window); + explicit QtWebBrowser(GMainWindow& parent); ~QtWebBrowser() override; - void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) override; + void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; + + void OpenExternalWebPage(std::string_view external_url, + std::function<void(Service::AM::Applets::WebExitReason, std::string)> + callback_) const override; signals: - void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; + void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) const; private: - void MainWindowUnpackRomFS(); - void MainWindowFinishedBrowsing(); + void MainWindowExtractOfflineRomFS(); + + void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, + std::string last_url); + + mutable std::function<void()> extract_romfs_callback; - std::function<void()> unpack_romfs_callback; - std::function<void()> finished_callback; + mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback; }; diff --git a/src/yuzu/applets/web_browser_scripts.h b/src/yuzu/applets/web_browser_scripts.h new file mode 100644 index 000000000..992837a85 --- /dev/null +++ b/src/yuzu/applets/web_browser_scripts.h @@ -0,0 +1,193 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +constexpr char NX_FONT_CSS[] = R"( +(function() { + css = document.createElement('style'); + css.type = 'text/css'; + css.id = 'nx_font'; + css.innerText = ` +/* FontStandard */ +@font-face { + font-family: 'FontStandard'; + src: url('%1') format('truetype'); +} + +/* FontChineseSimplified */ +@font-face { + font-family: 'FontChineseSimplified'; + src: url('%2') format('truetype'); +} + +/* FontExtendedChineseSimplified */ +@font-face { + font-family: 'FontExtendedChineseSimplified'; + src: url('%3') format('truetype'); +} + +/* FontChineseTraditional */ +@font-face { + font-family: 'FontChineseTraditional'; + src: url('%4') format('truetype'); +} + +/* FontKorean */ +@font-face { + font-family: 'FontKorean'; + src: url('%5') format('truetype'); +} + +/* FontNintendoExtended */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%6') format('truetype'); +} + +/* FontNintendoExtended2 */ +@font-face { + font-family: 'NintendoExt003'; + src: url('%7') format('truetype'); +} +`; + + document.head.appendChild(css); +})(); +)"; + +constexpr char LOAD_NX_FONT[] = R"( +(function() { + var elements = document.querySelectorAll("*"); + + for (var i = 0; i < elements.length; i++) { + var style = window.getComputedStyle(elements[i], null); + if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") || + style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) { + elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } else { + elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003"; + } + } +})(); +)"; + +constexpr char GAMEPAD_SCRIPT[] = R"( +window.addEventListener("gamepadconnected", function(e) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", + e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length); +}); + +window.addEventListener("gamepaddisconnected", function(e) { + console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id); +}); +)"; + +constexpr char WINDOW_NX_SCRIPT[] = R"( +var end_applet = false; +var yuzu_key_callbacks = []; + +(function() { + class WindowNX { + constructor() { + yuzu_key_callbacks[1] = function() { window.history.back(); }; + yuzu_key_callbacks[2] = function() { window.nx.endApplet(); }; + } + + addEventListener(type, listener, options) { + console.log("nx.addEventListener called, type=%s", type); + + window.addEventListener(type, listener, options); + } + + endApplet() { + console.log("nx.endApplet called"); + + end_applet = true; + } + + playSystemSe(system_se) { + console.log("nx.playSystemSe is not implemented, system_se=%s", system_se); + } + + sendMessage(message) { + console.log("nx.sendMessage is not implemented, message=%s", message); + } + + setCursorScrollSpeed(scroll_speed) { + console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed); + } + } + + class WindowNXFooter { + setAssign(key, label, func, option) { + console.log("nx.footer.setAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = func; + break; + case "B": + yuzu_key_callbacks[1] = func; + break; + case "X": + yuzu_key_callbacks[2] = func; + break; + case "Y": + yuzu_key_callbacks[3] = func; + break; + case "L": + yuzu_key_callbacks[6] = func; + break; + case "R": + yuzu_key_callbacks[7] = func; + break; + } + } + + setFixed(kind) { + console.log("nx.footer.setFixed is not implemented, kind=%s", kind); + } + + unsetAssign(key) { + console.log("nx.footer.unsetAssign called, key=%s", key); + + switch (key) { + case "A": + yuzu_key_callbacks[0] = function() {}; + break; + case "B": + yuzu_key_callbacks[1] = function() {}; + break; + case "X": + yuzu_key_callbacks[2] = function() {}; + break; + case "Y": + yuzu_key_callbacks[3] = function() {}; + break; + case "L": + yuzu_key_callbacks[6] = function() {}; + break; + case "R": + yuzu_key_callbacks[7] = function() {}; + break; + } + } + } + + class WindowNXPlayReport { + incrementCounter(counter_id) { + console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id); + } + + setCounterSetIdentifier(counter_id) { + console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id); + } + } + + window.nx = new WindowNX(); + window.nx.footer = new WindowNXFooter(); + window.nx.playReport = new WindowNXPlayReport(); +})(); +)"; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 408eac2b7..85ee2577d 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> @@ -18,7 +19,7 @@ #include <QOpenGLContext> #endif -#if !defined(WIN32) && HAS_VULKAN +#if !defined(WIN32) #include <qpa/qplatformnativeinterface.h> #endif @@ -34,7 +35,7 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/mouse/mouse_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -240,14 +241,12 @@ private: std::unique_ptr<Core::Frontend::GraphicsContext> context; }; -#ifdef HAS_VULKAN class VulkanRenderWidget : public RenderWidget { public: explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { windowHandle()->setSurfaceType(QWindow::VulkanSurface); } }; -#endif static Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. @@ -267,7 +266,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* Core::Frontend::EmuWindow::WindowSystemInfo wsi; wsi.type = GetWindowSystemType(); -#ifdef HAS_VULKAN // Our Win32 Qt external doesn't have the private API. #if defined(WIN32) || defined(__APPLE__) wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; @@ -280,7 +278,6 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr; #endif wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f; -#endif return wsi; } @@ -301,13 +298,19 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_, this->setMouseTracking(true); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); + connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram, + Qt::QueuedConnection); +} + +void GRenderWindow::ExecuteProgram(std::size_t program_index) { + emit ExecuteProgramSignal(program_index); } GRenderWindow::~GRenderWindow() { input_subsystem->Shutdown(); } -void GRenderWindow::PollEvents() { +void GRenderWindow::OnFrameDisplayed() { if (!first_frame) { first_frame = true; emit FirstFrameDisplayed(); @@ -381,44 +384,46 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { - // touch input is handled in TouchBeginEvent + // Touch input is handled in TouchBeginEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } auto pos = event->pos(); + const auto [x, y] = ScaleTouch(pos); + input_subsystem->GetMouse()->PressButton(x, y, event->button()); + if (event->button() == Qt::LeftButton) { - const auto [x, y] = ScaleTouch(pos); this->TouchPressed(x, y); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y()); } - QWidget::mousePressEvent(event); + + emit MouseActivity(); } void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { - // touch input is handled in TouchUpdateEvent + // Touch input is handled in TouchUpdateEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } auto pos = event->pos(); const auto [x, y] = ScaleTouch(pos); + input_subsystem->GetMouse()->MouseMove(x, y); this->TouchMoved(x, y); - input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y()); - QWidget::mouseMoveEvent(event); + + emit MouseActivity(); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { - // touch input is handled in TouchEndEvent + // Touch input is handled in TouchEndEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } + input_subsystem->GetMouse()->ReleaseButton(event->button()); + if (event->button() == Qt::LeftButton) { this->TouchReleased(); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->EndTilt(); } } @@ -560,6 +565,10 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p layout); } +bool GRenderWindow::IsLoadingComplete() const { + return first_frame; +} + void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); } @@ -585,37 +594,45 @@ bool GRenderWindow::InitializeOpenGL() { } bool GRenderWindow::InitializeVulkan() { -#ifdef HAS_VULKAN auto child = new VulkanRenderWidget(this); child_widget = child; child_widget->windowHandle()->create(); main_context = std::make_unique<DummyContext>(); return true; -#else - QMessageBox::critical(this, tr("Vulkan not available!"), - tr("yuzu has not been compiled with Vulkan support.")); - return false; -#endif } 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 +662,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; } @@ -666,3 +688,10 @@ void GRenderWindow::showEvent(QShowEvent* event) { connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged, Qt::UniqueConnection); } + +bool GRenderWindow::eventFilter(QObject* object, QEvent* event) { + if (event->type() == QEvent::HoverMove) { + emit MouseActivity(); + } + return false; +} diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index ca35cf831..339095509 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -131,7 +131,7 @@ public: ~GRenderWindow() override; // EmuWindow implementation. - void PollEvents() override; + void OnFrameDisplayed() override; bool IsShown() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; @@ -162,10 +162,18 @@ public: /// Destroy the previous run's child_widget which should also destroy the child_window void ReleaseRenderTarget(); + bool IsLoadingComplete() const; + void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); std::pair<u32, u32> ScaleTouch(const QPointF& pos) const; + /** + * Instructs the window to re-launch the application using the specified program_index. + * @param program_index Specifies the index within the application of the program to launch. + */ + void ExecuteProgram(std::size_t program_index); + public slots: void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); @@ -175,6 +183,8 @@ signals: /// Emitted when the window is closed void Closed(); void FirstFrameDisplayed(); + void ExecuteProgramSignal(std::size_t program_index); + void MouseActivity(); private: void TouchBeginEvent(const QTouchEvent* event); @@ -207,4 +217,5 @@ private: protected: void showEvent(QShowEvent* event) override; + bool eventFilter(QObject* object, QEvent* event) override; }; diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 649912557..a470056ef 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -72,7 +72,7 @@ void CompatDB::Submit() { void CompatDB::OnTestcaseSubmitted() { if (!testcase_watcher.result()) { QMessageBox::critical(this, tr("Communication error"), - tr("An error occured while sending the Testcase")); + tr("An error occurred while sending the Testcase")); button(NextButton)->setEnabled(true); button(NextButton)->setText(tr("Next")); button(CancelButton)->setVisible(true); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d2913d613..cda448718 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,7 +5,9 @@ #include <array> #include <QKeySequence> #include <QSettings> +#include "common/common_paths.h" #include "common/file_util.h" +#include "core/core.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" @@ -14,14 +16,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 +240,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 +502,24 @@ 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(); + Settings::values.emulate_analog_keyboard = + ReadSetting(QStringLiteral("emulate_analog_keyboard"), false).toBool(); + + ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true); + 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(); } @@ -500,22 +572,17 @@ void Config::ReadMotionTouchValues() { ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt(); Settings::values.touch_from_button_map_index = std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1); - Settings::values.udp_input_address = - ReadSetting(QStringLiteral("udp_input_address"), - QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) + Settings::values.udp_input_servers = + ReadSetting(QStringLiteral("udp_input_servers"), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV)) .toString() .toStdString(); - Settings::values.udp_input_port = static_cast<u16>( - ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT) - .toInt()); - Settings::values.udp_pad_index = - static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); } void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false); + ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), true); qt_config->endGroup(); } @@ -570,8 +637,6 @@ void Config::ReadDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini Settings::values.record_frame_times = qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); - Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); - Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); @@ -581,6 +646,8 @@ void Config::ReadDebuggingValues() { Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); Settings::values.disable_macro_jit = ReadSetting(QStringLiteral("disable_macro_jit"), false).toBool(); + Settings::values.extended_logging = + ReadSetting(QStringLiteral("extended_logging"), false).toBool(); qt_config->endGroup(); } @@ -697,6 +764,8 @@ void Config::ReadCpuValues() { ReadSetting(QStringLiteral("cpuopt_unsafe_unfuse_fma"), true).toBool(); Settings::values.cpuopt_unsafe_reduce_fp_error = ReadSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true).toBool(); + Settings::values.cpuopt_unsafe_inaccurate_nan = + ReadSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true).toBool(); } qt_config->endGroup(); @@ -716,10 +785,12 @@ void Config::ReadRendererValues() { QStringLiteral("use_disk_shader_cache"), true); ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0); ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation, - QStringLiteral("use_asynchronous_gpu_emulation"), false); + QStringLiteral("use_asynchronous_gpu_emulation"), true); + ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"), + true); ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), - false); + true); ReadSettingGlobal(Settings::values.use_asynchronous_shaders, QStringLiteral("use_asynchronous_shaders"), false); ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"), @@ -918,49 +989,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); + } + }(); + + const auto& player = Settings::values.players.GetValue()[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)); + WriteSetting(QStringLiteral("%1type").arg(player_prefix), + static_cast<u8>(player.controller_type), + static_cast<u8>(Settings::ControllerType::ProController)); - WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left, + 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)); } } @@ -1021,12 +1107,9 @@ void Config::SaveMotionTouchValues() { false); WriteSetting(QStringLiteral("touch_from_button_map"), Settings::values.touch_from_button_map_index, 0); - WriteSetting(QStringLiteral("udp_input_address"), - QString::fromStdString(Settings::values.udp_input_address), - QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); - WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, - InputCommon::CemuhookUDP::DEFAULT_PORT); - WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); + WriteSetting(QStringLiteral("udp_input_servers"), + QString::fromStdString(Settings::values.udp_input_servers), + QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_SRV)); qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps")); for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { @@ -1085,14 +1168,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, true); + 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")); @@ -1100,7 +1189,8 @@ 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); + WriteSetting(QStringLiteral("emulate_analog_keyboard"), + Settings::values.emulate_analog_keyboard, false); qt_config->endGroup(); } @@ -1108,7 +1198,7 @@ void Config::SaveControlValues() { void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); + WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, true); qt_config->endGroup(); } @@ -1146,8 +1236,6 @@ void Config::SaveDebuggingValues() { // Intentionally not using the QT default setting as this is intended to be changed in the ini qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); - WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); - WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), QString::fromStdString(Settings::values.program_args), QString{}); WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); @@ -1241,6 +1329,8 @@ void Config::SaveCpuValues() { Settings::values.cpuopt_unsafe_unfuse_fma, true); WriteSetting(QStringLiteral("cpuopt_unsafe_reduce_fp_error"), Settings::values.cpuopt_unsafe_reduce_fp_error, true); + WriteSetting(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), + Settings::values.cpuopt_unsafe_inaccurate_nan, true); } qt_config->endGroup(); @@ -1264,10 +1354,12 @@ void Config::SaveRendererValues() { static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)), Settings::values.gpu_accuracy.UsingGlobal(), 0); WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"), - Settings::values.use_asynchronous_gpu_emulation, false); + Settings::values.use_asynchronous_gpu_emulation, true); + WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation, + true); WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), - Settings::values.use_assembly_shaders, false); + Settings::values.use_assembly_shaders, true); WriteSettingGlobal(QStringLiteral("use_asynchronous_shaders"), Settings::values.use_asynchronous_shaders, false); WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, @@ -1501,13 +1593,27 @@ void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool void Config::Reload() { ReadValues(); - Settings::Sanitize(); // To apply default value changes SaveValues(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } 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_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index fa9124ecf..db9518798 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -25,8 +25,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); - ui->volume_label->setVisible(Settings::configuring_global); - ui->volume_combo_box->setVisible(!Settings::configuring_global); + ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); + ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal()); SetupPerGameUI(); @@ -51,7 +51,7 @@ void ConfigureAudio::SetConfiguration() { ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue()); - if (!Settings::configuring_global) { + if (!Settings::IsConfiguringGlobal()) { if (Settings::values.volume.UsingGlobal()) { ui->volume_combo_box->setCurrentIndex(0); ui->volume_slider->setEnabled(false); @@ -99,7 +99,7 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); @@ -165,7 +165,7 @@ void ConfigureAudio::RetranslateUI() { } void ConfigureAudio::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal()); ui->toggle_audio_stretching->setEnabled( Settings::values.enable_audio_stretching.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 37fcd6adc..d055cbd60 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -36,6 +36,8 @@ void ConfigureCpu::SetConfiguration() { ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma); ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error); + ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); + ui->cpuopt_unsafe_inaccurate_nan->setChecked(Settings::values.cpuopt_unsafe_inaccurate_nan); } void ConfigureCpu::AccuracyUpdated(int index) { @@ -61,6 +63,7 @@ void ConfigureCpu::ApplyConfiguration() { static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex()); Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked(); Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked(); + Settings::values.cpuopt_unsafe_inaccurate_nan = ui->cpuopt_unsafe_inaccurate_nan->isChecked(); } void ConfigureCpu::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index ebdd2e6e9..bcd0962e9 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui @@ -109,6 +109,18 @@ </property> </widget> </item> + <item> + <widget class="QCheckBox" name="cpuopt_unsafe_inaccurate_nan"> + <property name="text"> + <string>Inaccurate NaN handling</string> + </property> + <property name="toolTip"> + <string> + <div>This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.</div> + </string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 2bfe2c306..121873f95 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -28,9 +28,6 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::SetConfiguration() { - ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); - ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); @@ -41,11 +38,10 @@ void ConfigureDebug::SetConfiguration() { ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); + ui->extended_logging->setChecked(Settings::values.extended_logging); } void ConfigureDebug::ApplyConfiguration() { - Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); - Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); @@ -53,6 +49,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); + Settings::values.extended_logging = ui->extended_logging->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 9d6feb9f7..9186aa732 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>400</width> - <height>467</height> + <height>486</height> </rect> </property> <property name="windowTitle"> @@ -15,57 +15,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_1"> <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>GDB</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_1"> - <item> - <widget class="QCheckBox" name="toggle_gdbstub"> - <property name="text"> - <string>Enable GDB Stub</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>Port:</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="gdbport_spinbox"> - <property name="maximum"> - <number>65536</number> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - </layout> - </item> - <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> <string>Logging</string> @@ -90,7 +39,7 @@ <item> <widget class="QCheckBox" name="toggle_console"> <property name="text"> - <string>Show Log Console (Windows Only)</string> + <string>Show Log in Console</string> </property> </widget> </item> @@ -103,6 +52,34 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="extended_logging"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="toolTip"> + <string>When checked, the max size of the log increases from 100 MB to 1 GB</string> + </property> + <property name="text"> + <string>Enable Extended Logging</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>This will be reset automatically when yuzu closes.</string> + </property> + <property name="indent"> + <number>20</number> + </property> + </widget> + </item> </layout> </widget> </item> @@ -115,7 +92,7 @@ <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> - <widget class="QLabel" name="label_3"> + <widget class="QLabel" name="label_4"> <property name="text"> <string>Arguments String</string> </property> @@ -140,8 +117,8 @@ <property name="enabled"> <bool>true</bool> </property> - <property name="whatsThis"> - <string>When checked, the graphics API enters in a slower debugging mode</string> + <property name="toolTip"> + <string>When checked, the graphics API enters a slower debugging mode</string> </property> <property name="text"> <string>Enable Graphics Debugging</string> @@ -153,8 +130,8 @@ <property name="enabled"> <bool>true</bool> </property> - <property name="whatsThis"> - <string>When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower</string> + <property name="toolTip"> + <string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string> </property> <property name="text"> <string>Disable Macro JIT</string> @@ -169,7 +146,7 @@ <property name="title"> <string>Dump</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> + <layout class="QVBoxLayout" name="verticalLayout_7"> <item> <widget class="QCheckBox" name="reporting_services"> <property name="text"> @@ -178,7 +155,7 @@ </widget> </item> <item> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="label_5"> <property name="font"> <font> <italic>true</italic> @@ -200,7 +177,7 @@ <property name="title"> <string>Advanced</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_7"> + <layout class="QVBoxLayout" name="verticalLayout_8"> <item> <widget class="QCheckBox" name="quest_flag"> <property name="text"> @@ -230,8 +207,6 @@ </layout> </widget> <tabstops> - <tabstop>toggle_gdbstub</tabstop> - <tabstop>gdbport_spinbox</tabstop> <tabstop>log_filter_edit</tabstop> <tabstop>toggle_console</tabstop> <tabstop>open_log_button</tabstop> @@ -241,22 +216,5 @@ <tabstop>quest_flag</tabstop> </tabstops> <resources/> - <connections> - <connection> - <sender>toggle_gdbstub</sender> - <signal>toggled(bool)</signal> - <receiver>gdbport_spinbox</receiver> - <slot>setEnabled(bool)</slot> - <hints> - <hint type="sourcelabel"> - <x>84</x> - <y>157</y> - </hint> - <hint type="destinationlabel"> - <x>342</x> - <y>158</y> - </hint> - </hints> - </connection> - </connections> + <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_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 8186929a6..b33f8437a 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -5,6 +5,7 @@ #include <QHash> #include <QListWidgetItem> #include <QSignalBlocker> +#include "core/core.h" #include "core/settings.h" #include "ui_configure.h" #include "yuzu/configuration/config.h" @@ -15,7 +16,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, InputCommon::InputSubsystem* input_subsystem) : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { - Settings::configuring_global = true; + Settings::SetConfiguringGlobal(true); ui->setupUi(this); ui->hotkeysTab->Populate(registry); @@ -54,7 +55,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->debugTab->ApplyConfiguration(); ui->webTab->ApplyConfiguration(); ui->serviceTab->ApplyConfiguration(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); Settings::LogSettings(); } diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 830096ea0..d4d29d422 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -19,7 +19,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) SetConfiguration(); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); } @@ -41,7 +41,7 @@ void ConfigureGeneral::SetConfiguration() { ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); } else { ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() && @@ -50,7 +50,7 @@ void ConfigureGeneral::SetConfiguration() { } void ConfigureGeneral::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); @@ -93,7 +93,7 @@ void ConfigureGeneral::RetranslateUI() { } void ConfigureGeneral::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal()); ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 07d818548..b78a5dff0 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,22 +4,17 @@ #include <QColorDialog> #include <QComboBox> -#ifdef HAS_VULKAN #include <QVulkanInstance> -#endif #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" -#ifdef HAS_VULKAN -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#endif - ConfigureGraphics::ConfigureGraphics(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGraphics) { vulkan_device = Settings::values.vulkan_device.GetValue(); @@ -33,7 +28,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { UpdateDeviceComboBox(); - if (!Settings::configuring_global) { + if (!Settings::IsConfiguringGlobal()) { ConfigurationShared::SetHighlight( ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX); } @@ -49,8 +44,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) UpdateBackgroundColorButton(new_bg_color); }); - ui->bg_label->setVisible(Settings::configuring_global); - ui->bg_combobox->setVisible(!Settings::configuring_global); + ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); + ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); } void ConfigureGraphics::UpdateDeviceSelection(int device) { @@ -70,11 +65,13 @@ void ConfigureGraphics::SetConfiguration() { ui->api->setEnabled(runtime_lock); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); ui->use_disk_shader_cache->setEnabled(runtime_lock); + ui->use_nvdec_emulation->setEnabled(runtime_lock); ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); ui->use_asynchronous_gpu_emulation->setChecked( Settings::values.use_asynchronous_gpu_emulation.GetValue()); + ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); } else { @@ -98,7 +95,7 @@ void ConfigureGraphics::SetConfiguration() { } void ConfigureGraphics::ApplyConfiguration() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value if (Settings::values.renderer_backend.UsingGlobal()) { Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend()); @@ -116,6 +113,9 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.use_asynchronous_gpu_emulation.SetValue( ui->use_asynchronous_gpu_emulation->isChecked()); } + if (Settings::values.use_nvdec_emulation.UsingGlobal()) { + Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked()); + } if (Settings::values.bg_red.UsingGlobal()) { Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF())); Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF())); @@ -144,6 +144,8 @@ void ConfigureGraphics::ApplyConfiguration() { ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation, ui->use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, + ui->use_nvdec_emulation, use_nvdec_emulation); if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { Settings::values.bg_red.SetGlobal(true); @@ -187,7 +189,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() { bool enabled = false; - if (!Settings::configuring_global && + if (!Settings::IsConfiguringGlobal() && ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { vulkan_device = Settings::values.vulkan_device.GetValue(); } @@ -205,22 +207,20 @@ void ConfigureGraphics::UpdateDeviceComboBox() { break; } // If in per-game config and use global is selected, don't enable. - enabled &= !(!Settings::configuring_global && + enabled &= !(!Settings::IsConfiguringGlobal() && ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX); ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn()); } void ConfigureGraphics::RetrieveVulkanDevices() { -#ifdef HAS_VULKAN vulkan_devices.clear(); - for (auto& name : Vulkan::RendererVulkan::EnumerateDevices()) { + for (const auto& name : Vulkan::RendererVulkan::EnumerateDevices()) { vulkan_devices.push_back(QString::fromStdString(name)); } -#endif } Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); } @@ -234,12 +234,13 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { } void ConfigureGraphics::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal()); ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal()); ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); ui->use_asynchronous_gpu_emulation->setEnabled( Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); + ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); @@ -253,6 +254,8 @@ void ConfigureGraphics::SetupPerGameUI() { ConfigurationShared::SetColoredTristate( ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); + ConfigurationShared::SetColoredTristate( + ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, Settings::values.use_asynchronous_gpu_emulation, use_asynchronous_gpu_emulation); diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index b4961f719..1fefc88eb 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -46,6 +46,7 @@ private: std::unique_ptr<Ui::ConfigureGraphics> ui; QColor bg_color; + ConfigurationShared::CheckState use_nvdec_emulation; ConfigurationShared::CheckState use_disk_shader_cache; ConfigurationShared::CheckState use_asynchronous_gpu_emulation; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 62aa337e7..58486eb1e 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -98,6 +98,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_nvdec_emulation"> + <property name="text"> + <string>Use NVDEC emulation</string> + </property> + </widget> + </item> + <item> <widget class="QWidget" name="aspect_ratio_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_6"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 73f276949..383c7bac8 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -32,7 +32,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( static_cast<int>(Settings::values.gpu_accuracy.GetValue())); ui->anisotropic_filtering_combobox->setCurrentIndex( @@ -52,9 +52,9 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots) const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>( ui->gpu_accuracy->currentIndex() - - ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); + ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Must guard in case of a during-game configuration when set to be game-specific. if (Settings::values.gpu_accuracy.UsingGlobal()) { Settings::values.gpu_accuracy.SetValue(gpu_accuracy); @@ -118,7 +118,7 @@ void ConfigureGraphicsAdvanced::RetranslateUI() { void ConfigureGraphicsAdvanced::SetupPerGameUI() { // Disable if not global (only happens during game) - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal()); ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal()); ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal()); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 2725fcb2b..567a36d9b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <memory> +#include <thread> #include <QSignalBlocker> #include <QTimer> @@ -23,6 +24,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 +67,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 +77,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 +125,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 +148,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 +160,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); }); @@ -165,18 +182,28 @@ QList<QWidget*> ConfigureInput::GetSubTabs() const { } void ConfigureInput::ApplyConfiguration() { - for (auto controller : player_controllers) { + for (auto* controller : player_controllers) { controller->ApplyConfiguration(); + controller->TryDisconnectSelectedController(); + } + + // 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(60ms); + + for (auto* controller : player_controllers) { + controller->TryConnectSelectedController(); } 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 +220,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 +258,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 +269,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..4e557bc6f 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(); }); @@ -121,12 +121,13 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); + Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); } 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 +140,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())); } } @@ -147,6 +148,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); + ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); UpdateUIEnabled(); 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..f207e5d3b 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/> @@ -2546,14 +2546,27 @@ </property> </widget> </item> - <item row="4" column="2"> + <item row="1" column="0"> + <widget class="QCheckBox" name="emulate_analog_keyboard"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Emulate Analog with Keyboard Input</string> + </property> + </widget> + </item> + <item row="5" column="2"> <widget class="QPushButton" name="touchscreen_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="2" column="1"> <spacer name="horizontalSpacer_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -2569,21 +2582,21 @@ </property> </spacer> </item> - <item row="1" column="2"> + <item row="2" column="2"> <widget class="QPushButton" name="mouse_advanced"> <property name="text"> <string>Advanced</string> </property> </widget> </item> - <item row="4" column="0"> + <item row="5" column="0"> <widget class="QCheckBox" name="touchscreen_enabled"> <property name="text"> <string>Touchscreen</string> </property> </widget> </item> - <item row="1" column="0"> + <item row="2" column="0"> <widget class="QCheckBox" name="mouse_enabled"> <property name="minimumSize"> <size> @@ -2596,28 +2609,28 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="motion_touch"> <property name="text"> <string>Motion / Touch</string> </property> </widget> </item> - <item row="6" column="2"> + <item row="7" column="2"> <widget class="QPushButton" name="buttonMotionTouch"> <property name="text"> <string>Configure</string> </property> </widget> </item> - <item row="5" column="0"> + <item row="6" column="0"> <widget class="QCheckBox" name="debug_enabled"> <property name="text"> <string>Debug Controller</string> </property> </widget> </item> - <item row="5" column="2"> + <item row="6" column="2"> <widget class="QPushButton" name="debug_configure"> <property name="text"> <string>Configure</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 698cb1940..46ea026e4 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -18,12 +18,16 @@ #include "core/hle/service/sm/sm.h" #include "input_common/gcadapter/gc_poller.h" #include "input_common/main.h" +#include "input_common/mouse/mouse_poller.h" #include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_vibration.h" +#include "yuzu/configuration/input_profiles.h" +#include "yuzu/util/limitable_input_dialog.h" -constexpr std::size_t HANDHELD_INDEX = 8; +using namespace Service::HID; const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> ConfigureInputPlayer::analog_sub_buttons{{ @@ -35,6 +39,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()}; @@ -43,48 +49,12 @@ void UpdateController(Settings::ControllerType controller_type, std::size_t npad } Service::SM::ServiceManager& sm = system.ServiceManager(); - auto& npad = - sm.GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected); } -/// Maps the controller type combobox index to Controller Type enum -constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) { - switch (index) { - case 0: - default: - return Settings::ControllerType::ProController; - case 1: - return Settings::ControllerType::DualJoyconDetached; - case 2: - return Settings::ControllerType::LeftJoycon; - case 3: - return Settings::ControllerType::RightJoycon; - case 4: - return Settings::ControllerType::Handheld; - } -} - -/// Maps the Controller Type enum to controller type combobox index -constexpr int GetIndexFromControllerType(Settings::ControllerType type) { - switch (type) { - case Settings::ControllerType::ProController: - default: - return 0; - case Settings::ControllerType::DualJoyconDetached: - return 1; - case Settings::ControllerType::LeftJoycon: - return 2; - case Settings::ControllerType::RightJoycon: - return 3; - case Settings::ControllerType::Handheld: - return 4; - } -} - QString GetKeyName(int key_code) { switch (key_code) { case Qt::LeftButton: @@ -182,6 +152,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return {}; } + if (param.Get("engine", "") == "mouse") { + if (param.Has("button")) { + const QString button_str = QString::number(int(param.Get("button", 0))); + return QObject::tr("Click %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + return QObject::tr("[unknown]"); } @@ -194,41 +172,31 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); } - if (param.Get("engine", "") == "sdl") { + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { if (dir == "modifier") { return QObject::tr("[unused]"); } - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("Axis %1").arg(axis_x_str); - } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("Axis %1").arg(axis_y_str); + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - return {}; - } - - if (param.Get("engine", "") == "gcpad") { - if (dir == "modifier") { - return QObject::tr("[unused]"); + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); } - - if (dir == "left" || dir == "right") { - const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); - - return QObject::tr("GC Axis %1").arg(axis_x_str); + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } - - if (dir == "up" || dir == "down") { - const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); - - return QObject::tr("GC Axis %1").arg(axis_y_str); + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); } return {}; @@ -240,10 +208,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); @@ -256,11 +225,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, }; - mod_buttons = { - ui->buttonLStickMod, - ui->buttonRStickMod, - }; - analog_map_buttons = {{ { ui->buttonLStickUp, @@ -284,6 +248,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; + analog_map_modifier_button = {ui->buttonLStickMod, ui->buttonRStickMod}; analog_map_modifier_label = {ui->labelLStickModifierRange, ui->labelRStickModifierRange}; analog_map_modifier_slider = {ui->sliderLStickModifierRange, ui->sliderRStickModifierRange}; analog_map_range_groupbox = {ui->buttonLStickRangeGroup, ui->buttonRStickRangeGroup}; @@ -370,6 +335,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) { @@ -388,26 +365,51 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i analogs_param[analog_id].Clear(); analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); }); + context_menu.addAction(tr("Invert axis"), [&] { + if (sub_button_id == 2 || sub_button_id == 3) { + const bool invert_value = + analogs_param[analog_id].Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_x", invert_str); + } + if (sub_button_id == 0 || sub_button_id == 1) { + const bool invert_value = + analogs_param[analog_id].Get("invert_y", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + analogs_param[analog_id].Set("invert_y", invert_str); + } + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + }); context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( menu_location)); }); } // Handle clicks for the modifier buttons as well. - ConfigureButtonClick(mod_buttons[analog_id], &stick_mod_param[analog_id], - Config::default_stick_mod[analog_id], - InputCommon::Polling::DeviceType::Button); + connect(analog_map_modifier_button[analog_id], &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_modifier_button[analog_id], + [=, this](const Common::ParamPackage& params) { + analogs_param[analog_id].Set("modifier", params.Serialize()); + }, + InputCommon::Polling::DeviceType::Button); + }); - mod_buttons[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); + analog_map_modifier_button[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu); - connect(mod_buttons[analog_id], &QPushButton::customContextMenuRequested, + connect(analog_map_modifier_button[analog_id], &QPushButton::customContextMenuRequested, [=, this](const QPoint& menu_location) { QMenu context_menu; context_menu.addAction(tr("Clear"), [&] { - stick_mod_param[analog_id].Clear(); - mod_buttons[analog_id]->setText(tr("[not set]")); + analogs_param[analog_id].Set("modifier", ""); + analog_map_modifier_button[analog_id]->setText(tr("[not set]")); }); - context_menu.exec(mod_buttons[analog_id]->mapToGlobal(menu_location)); + context_menu.exec( + analog_map_modifier_button[analog_id]->mapToGlobal(menu_location)); }); connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), @@ -434,18 +436,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { emit Connected(checked); }); - // Set up controller type. Only Player 1 can choose Handheld. - ui->comboControllerType->clear(); - - QStringList controller_types = { - tr("Pro Controller"), - tr("Dual Joycons"), - tr("Left Joycon"), - tr("Right Joycon"), - }; - if (player_index == 0) { - controller_types.append(tr("Handheld")); connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) { emit HandheldStateChanged(GetControllerTypeFromIndex(index) == @@ -453,17 +444,17 @@ 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"), - }; - ui->comboControllerType->addItems(debug_controller_types); + ui->comboControllerType->addItem(tr("Pro Controller")); } else { - ui->comboControllerType->addItems(controller_types); + SetConnectableControllers(); } UpdateControllerIcon(); @@ -475,11 +466,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(); }); @@ -490,14 +482,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; } @@ -509,15 +501,54 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i return; } } + if (input_subsystem->GetMouseButtons()->IsPolling()) { + params = input_subsystem->GetMouseButtons()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseAnalogs()->IsPolling()) { + params = input_subsystem->GetMouseAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseMotions()->IsPolling()) { + params = input_subsystem->GetMouseMotions()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseTouch()->IsPolling()) { + params = input_subsystem->GetMouseTouch()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } 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 @@ -527,7 +558,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; @@ -541,33 +572,71 @@ void ConfigureInputPlayer::ApplyConfiguration() { } auto& motions = player.motions; + std::transform(motions_param.begin(), motions_param.end(), motions.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); +} + +void ConfigureInputPlayer::TryConnectSelectedController() { + auto& player = Settings::values.players.GetValue()[player_index]; - 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 + player.controller_type = controller_type; + player.connected = player_connected; + + ConfigureVibration::SetVibrationDevices(player_index); + + // Connect/Disconnect Handheld depending on Player 1's controller configuration. + 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); } - UpdateController(player.controller_type, player_index, player.connected); - UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected); + if (!player.connected) { + return; + } + + UpdateController(controller_type, player_index, player_connected); +} + +void ConfigureInputPlayer::TryDisconnectSelectedController() { + const auto& player = Settings::values.players.GetValue()[player_index]; + + const auto controller_type = + GetControllerTypeFromIndex(ui->comboControllerType->currentIndex()); + const auto player_connected = ui->groupConnectedController->isChecked() && + controller_type != Settings::ControllerType::Handheld; + + // Do not do anything if the controller configuration has not changed. + if (player.controller_type == controller_type && player.connected == player_connected) { + return; + } + + // Disconnect the controller first. + UpdateController(controller_type, player_index, false); +} + +void ConfigureInputPlayer::showEvent(QShowEvent* event) { + if (bottom_row == nullptr) { + return; + } + QWidget::showEvent(event); + ui->main->addWidget(bottom_row); } void ConfigureInputPlayer::changeEvent(QEvent* event) { @@ -584,7 +653,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(), @@ -602,52 +671,84 @@ void ConfigureInputPlayer::LoadConfiguration() { } UpdateUI(); + UpdateInputDeviceCombobox(); if (debug) { return; } - ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type)); + ui->comboControllerType->setCurrentIndex(GetIndexFromControllerType(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(); + + const auto current_engine = button_param->Get("engine", ""); + const auto current_guid = button_param->Get("guid", ""); + const auto current_port = button_param->Get("port", ""); - stick_mod_param[analog_id] = Common::ParamPackage( - InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id])); + 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() { @@ -669,8 +770,6 @@ void ConfigureInputPlayer::ClearAll() { analogs_param[analog_id].Clear(); } - - stick_mod_param[analog_id].Clear(); } for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) { @@ -707,7 +806,8 @@ void ConfigureInputPlayer::UpdateUI() { AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); } - mod_buttons[analog_id]->setText(ButtonToText(stick_mod_param[analog_id])); + analog_map_modifier_button[analog_id]->setText( + ButtonToText(Common::ParamPackage{analogs_param[analog_id].Get("modifier", "")})); const auto deadzone_label = analog_map_deadzone_label[analog_id]; const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; @@ -719,8 +819,9 @@ void ConfigureInputPlayer::UpdateUI() { int slider_value; auto& param = analogs_param[analog_id]; - const bool is_controller = - param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; + const bool is_controller = param.Get("engine", "") == "sdl" || + param.Get("engine", "") == "gcpad" || + param.Get("engine", "") == "mouse"; if (is_controller) { if (!param.Has("deadzone")) { @@ -751,117 +852,88 @@ void ConfigureInputPlayer::UpdateUI() { } } -void ConfigureInputPlayer::UpdateMappingWithDefaults() { - if (ui->comboDevices->currentIndex() < 2) { - return; - } - const auto& device = input_devices[ui->comboDevices->currentIndex()]; - auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); - auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); - for (std::size_t i = 0; i < buttons_param.size(); ++i) { - buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; - } - for (std::size_t i = 0; i < analogs_param.size(); ++i) { - analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; - } - - UpdateUI(); -} - -void ConfigureInputPlayer::HandleClick( - QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type) { - if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { - button->setText(tr("Shake!")); - } else { - button->setText(tr("[waiting]")); - } - button->setFocus(); +void ConfigureInputPlayer::SetConnectableControllers() { + const auto add_controllers = [this](bool enable_all, + Controller_NPad::NpadStyleSet npad_style_set = {}) { + index_controller_type_pairs.clear(); + ui->comboControllerType->clear(); - // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a - // controller, then they don't want keyboard/mouse input - want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + if (enable_all || npad_style_set.pro_controller == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::ProController); + ui->comboControllerType->addItem(tr("Pro Controller")); + } - input_setter = new_input_setter; + if (enable_all || npad_style_set.joycon_dual == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::DualJoyconDetached); + ui->comboControllerType->addItem(tr("Dual Joycons")); + } - device_pollers = input_subsystem->GetPollers(type); + if (enable_all || npad_style_set.joycon_left == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::LeftJoycon); + ui->comboControllerType->addItem(tr("Left Joycon")); + } - for (auto& poller : device_pollers) { - poller->Start(); - } + if (enable_all || npad_style_set.joycon_right == 1) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::RightJoycon); + ui->comboControllerType->addItem(tr("Right Joycon")); + } - QWidget::grabMouse(); - QWidget::grabKeyboard(); + if (player_index == 0 && (enable_all || npad_style_set.handheld == 1)) { + index_controller_type_pairs.emplace_back(ui->comboControllerType->count(), + Settings::ControllerType::Handheld); + ui->comboControllerType->addItem(tr("Handheld")); + } + }; - if (type == InputCommon::Polling::DeviceType::Button) { - input_subsystem->GetGCButtons()->BeginConfiguration(); - } else { - input_subsystem->GetGCAnalogs()->BeginConfiguration(); - } + Core::System& system{Core::System::GetInstance()}; - if (type == InputCommon::Polling::DeviceType::Motion) { - input_subsystem->GetUDPMotions()->BeginConfiguration(); + if (!system.IsPoweredOn()) { + add_controllers(true); + return; } - timeout_timer->start(2500); // Cancel after 2.5 seconds - poll_timer->start(50); // Check for new inputs every 50ms -} - -void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); - } + Service::SM::ServiceManager& sm = system.ServiceManager(); - QWidget::releaseMouse(); - QWidget::releaseKeyboard(); + auto& npad = sm.GetService<Hid>("hid")->GetAppletResource()->GetController<Controller_NPad>( + HidController::NPad); - input_subsystem->GetGCButtons()->EndConfiguration(); - input_subsystem->GetGCAnalogs()->EndConfiguration(); + add_controllers(false, npad.GetSupportedStyleSet()); +} - input_subsystem->GetUDPMotions()->EndConfiguration(); +Settings::ControllerType ConfigureInputPlayer::GetControllerTypeFromIndex(int index) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [index](const auto& pair) { return pair.first == index; }); - if (!abort) { - (*input_setter)(params); + if (it == index_controller_type_pairs.end()) { + return Settings::ControllerType::ProController; } - UpdateUI(); - input_setter = std::nullopt; + return it->second; } -void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { - if (!input_setter || !event) { - return; - } +int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType type) const { + const auto it = + std::find_if(index_controller_type_pairs.begin(), index_controller_type_pairs.end(), + [type](const auto& pair) { return pair.second == type; }); - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, - false); - } else { - // We don't want any mouse buttons, so don't stop polling - return; + if (it == index_controller_type_pairs.end()) { + return -1; } - SetPollingResult({}, true); + return it->first; } -void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) { - return; - } - - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } +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")), {}); } - - SetPollingResult({}, true); } void ConfigureInputPlayer::UpdateControllerIcon() { @@ -884,7 +956,7 @@ void ConfigureInputPlayer::UpdateControllerIcon() { } }(); - const QString theme = [this] { + const QString theme = [] { if (QIcon::themeName().contains(QStringLiteral("dark"))) { return QStringLiteral("_dark"); } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) { @@ -985,14 +1057,267 @@ void ConfigureInputPlayer::UpdateMotionButtons() { } } -void ConfigureInputPlayer::showEvent(QShowEvent* event) { - if (bottom_row == nullptr) { +void ConfigureInputPlayer::UpdateMappingWithDefaults() { + if (ui->comboDevices->currentIndex() == 0) { return; } - QWidget::showEvent(event); - ui->main->addWidget(bottom_row); + + 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); + for (std::size_t i = 0; i < buttons_param.size(); ++i) { + buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; + } + for (std::size_t i = 0; i < analogs_param.size(); ++i) { + analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; + } + + UpdateUI(); } -void ConfigureInputPlayer::ConnectPlayer(bool connected) { - ui->groupConnectedController->setChecked(connected); +void ConfigureInputPlayer::HandleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) { + button->setText(tr("Shake!")); + } else { + button->setText(tr("[waiting]")); + } + button->setFocus(); + + // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a + // controller, then they don't want keyboard/mouse input + want_keyboard_mouse = ui->comboDevices->currentIndex() < 2; + + input_setter = new_input_setter; + + device_pollers = input_subsystem->GetPollers(type); + + for (auto& poller : device_pollers) { + poller->Start(); + } + + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetGCButtons()->BeginConfiguration(); + } else { + input_subsystem->GetGCAnalogs()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetUDPMotions()->BeginConfiguration(); + } + + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetMouseButtons()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + input_subsystem->GetMouseAnalogs()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetMouseMotions()->BeginConfiguration(); + } else { + input_subsystem->GetMouseTouch()->BeginConfiguration(); + } + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(50); // Check for new inputs every 50ms +} + +void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) { + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + input_subsystem->GetGCButtons()->EndConfiguration(); + input_subsystem->GetGCAnalogs()->EndConfiguration(); + + input_subsystem->GetUDPMotions()->EndConfiguration(); + + input_subsystem->GetMouseButtons()->EndConfiguration(); + input_subsystem->GetMouseAnalogs()->EndConfiguration(); + input_subsystem->GetMouseMotions()->EndConfiguration(); + input_subsystem->GetMouseTouch()->EndConfiguration(); + + if (!abort) { + (*input_setter)(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; + } + + input_subsystem->GetMouse()->PressButton(0, 0, event->button()); +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_mouse) { + SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + + SetPollingResult({}, true); +} + +void ConfigureInputPlayer::CreateProfile() { + const auto profile_name = + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); + + if (profile_name.isEmpty()) { + return; + } + + if (!InputProfiles::IsProfileNameValid(profile_name.toStdString())) { + QMessageBox::critical(this, tr("Create Input Profile"), + tr("The given profile name is not valid!")); + return; + } + + ApplyConfiguration(); + + 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; + } + + 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; + } + + 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::LoadProfile() { + const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex()); + + if (profile_name.isEmpty()) { + return; + } + + 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::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; + } +} + +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 ce443dec5..c4ae50de7 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -9,6 +9,7 @@ #include <memory> #include <optional> #include <string> +#include <vector> #include <QWidget> @@ -26,6 +27,8 @@ class QString; class QTimer; class QWidget; +class InputProfiles; + namespace InputCommon { class InputSubsystem; } @@ -45,14 +48,32 @@ 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(); + /** + * Attempts to connect the currently selected controller in the HID backend. + * This function will not do anything if it is not connected in the frontend. + */ + void TryConnectSelectedController(); + + /** + * Attempts to disconnect the currently selected controller in the HID backend. + * This function will not do anything if the configuration has not changed. + */ + void TryDisconnectSelectedController(); + + /// 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 +81,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 +88,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 +113,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 +125,17 @@ private: /// Update UI to reflect current configuration. void UpdateUI(); - /// Update the controller selection combobox - void UpdateControllerCombobox(); + /// Sets the available controllers. + void SetConnectableControllers(); + + /// Gets the Controller Type for a given controller combobox index. + Settings::ControllerType GetControllerTypeFromIndex(int index) const; + + /// Gets the controller combobox index for a given Controller Type. + int GetIndexFromControllerType(Settings::ControllerType type) const; + + /// Update the available input devices. + void UpdateInputDevices(); /// Update the current controller icon. void UpdateControllerIcon(); @@ -113,6 +149,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,9 +168,14 @@ private: InputCommon::InputSubsystem* input_subsystem; + InputProfiles* profiles; + std::unique_ptr<QTimer> timeout_timer; std::unique_ptr<QTimer> poll_timer; + /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. + std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs; + static constexpr int PLAYER_COUNT = 8; std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; @@ -131,26 +184,25 @@ private: std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; - std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> stick_mod_param; std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param; static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; /// Each button input is represented by a QPushButton. std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; - /// Each motion input is represented by a QPushButton. - std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; - /// Extra buttons for the modifiers. - std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> mod_buttons; /// A group of four QPushButtons represent one analog input. The buttons each represent up, /// down, left, right, respectively. std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> analog_map_buttons; + /// Each motion input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map; + std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label; std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider; std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox; + std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button; std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label; std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider; std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox; @@ -160,12 +212,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.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index c7d085151..eb8eacbf9 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -3,10 +3,12 @@ // Refer to the license.txt file included. #include <array> +#include <sstream> #include <QCloseEvent> #include <QLabel> #include <QMessageBox> #include <QPushButton> +#include <QStringListModel> #include <QVBoxLayout> #include "common/logging/log.h" #include "core/settings.h" @@ -49,6 +51,8 @@ CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent, case CalibrationConfigurationJob::Status::Completed: text = tr("Configuration completed!"); break; + default: + break; } QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text)); if (status == CalibrationConfigurationJob::Status::Completed) { @@ -74,11 +78,6 @@ void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) { cancel_button->setText(text); } -constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{ - {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")}, - {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, -}}; - constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{ {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")}, {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")}, @@ -89,9 +88,6 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, : QDialog(parent), input_subsystem{input_subsystem_}, ui(std::make_unique<Ui::ConfigureMotionTouch>()) { ui->setupUi(this); - for (const auto& [provider, name] : MotionProviders) { - ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider)); - } for (const auto& [provider, name] : TouchProviders) { ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider)); } @@ -116,8 +112,6 @@ void ConfigureMotionTouch::SetConfiguration() { const std::string motion_engine = motion_param.Get("engine", "motion_emu"); const std::string touch_engine = touch_param.Get("engine", "emu_window"); - ui->motion_provider->setCurrentIndex( - ui->motion_provider->findData(QString::fromStdString(motion_engine))); ui->touch_provider->setCurrentIndex( ui->touch_provider->findData(QString::fromStdString(touch_engine))); ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button); @@ -133,23 +127,30 @@ void ConfigureMotionTouch::SetConfiguration() { max_x = touch_param.Get("max_x", 1800); max_y = touch_param.Get("max_y", 850); - ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address)); - ui->udp_port->setText(QString::number(Settings::values.udp_input_port)); - ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index); + ui->udp_server->setText(QString::fromStdString("127.0.0.1")); + ui->udp_port->setText(QString::number(26760)); + + udp_server_list_model = new QStringListModel(this); + udp_server_list_model->setStringList({}); + ui->udp_server_list->setModel(udp_server_list_model); + + std::stringstream ss(Settings::values.udp_input_servers); + std::string token; + + while (std::getline(ss, token, ',')) { + const int row = udp_server_list_model->rowCount(); + udp_server_list_model->insertRows(row, 1); + const QModelIndex index = udp_server_list_model->index(row); + udp_server_list_model->setData(index, QString::fromStdString(token)); + } } void ConfigureMotionTouch::UpdateUiDisplay() { - const QString motion_engine = ui->motion_provider->currentData().toString(); const QString touch_engine = ui->touch_provider->currentData().toString(); const QString cemuhook_udp = QStringLiteral("cemuhookudp"); - if (motion_engine == QStringLiteral("motion_emu")) { - ui->motion_sensitivity_label->setVisible(true); - ui->motion_sensitivity->setVisible(true); - } else { - ui->motion_sensitivity_label->setVisible(false); - ui->motion_sensitivity->setVisible(false); - } + ui->motion_sensitivity_label->setVisible(true); + ui->motion_sensitivity->setVisible(true); if (touch_engine == cemuhook_udp) { ui->touch_calibration->setVisible(true); @@ -163,19 +164,15 @@ void ConfigureMotionTouch::UpdateUiDisplay() { ui->touch_calibration_label->setVisible(false); } - if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) { - ui->udp_config_group_box->setVisible(true); - } else { - ui->udp_config_group_box->setVisible(false); - } + ui->udp_config_group_box->setVisible(true); } void ConfigureMotionTouch::ConnectEvents() { - connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, - [this](int index) { UpdateUiDisplay(); }); connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { UpdateUiDisplay(); }); connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest); + connect(ui->udp_add, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPAddServer); + connect(ui->udp_remove, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPDeleteServer); connect(ui->touch_calibration_config, &QPushButton::clicked, this, &ConfigureMotionTouch::OnConfigureTouchCalibration); connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this, @@ -187,13 +184,58 @@ void ConfigureMotionTouch::ConnectEvents() { }); } +void ConfigureMotionTouch::OnUDPAddServer() { + QRegExp re(tr(R"re(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4]" + "[0-9]|[01]?[0-9][0-9]?)$)re")); // a valid ip address + bool ok; + QString port_text = ui->udp_port->text(); + QString server_text = ui->udp_server->text(); + const QString server_string = tr("%1:%2").arg(server_text, port_text); + int port_number = port_text.toInt(&ok, 10); + int row = udp_server_list_model->rowCount(); + + if (!ok) { + QMessageBox::warning(this, tr("yuzu"), tr("Port number has invalid characters")); + return; + } + if (port_number < 0 || port_number > 65353) { + QMessageBox::warning(this, tr("yuzu"), tr("Port has to be in range 0 and 65353")); + return; + } + if (!re.exactMatch(server_text)) { + QMessageBox::warning(this, tr("yuzu"), tr("IP address is not valid")); + return; + } + // Search for duplicates + for (const auto& item : udp_server_list_model->stringList()) { + if (item == server_string) { + QMessageBox::warning(this, tr("yuzu"), tr("This UDP server already exists")); + return; + } + } + // Limit server count to 8 + if (row == 8) { + QMessageBox::warning(this, tr("yuzu"), tr("Unable to add more than 8 servers")); + return; + } + + udp_server_list_model->insertRows(row, 1); + QModelIndex index = udp_server_list_model->index(row); + udp_server_list_model->setData(index, server_string); + ui->udp_server_list->setCurrentIndex(index); +} + +void ConfigureMotionTouch::OnUDPDeleteServer() { + udp_server_list_model->removeRows(ui->udp_server_list->currentIndex().row(), 1); +} + void ConfigureMotionTouch::OnCemuhookUDPTest() { ui->udp_test->setEnabled(false); ui->udp_test->setText(tr("Testing")); udp_test_in_progress = true; InputCommon::CemuhookUDP::TestCommunication( - ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), - static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872, + ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), 0, + 24872, [this] { LOG_INFO(Frontend, "UDP input test success"); QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true)); @@ -207,9 +249,9 @@ void ConfigureMotionTouch::OnCemuhookUDPTest() { void ConfigureMotionTouch::OnConfigureTouchCalibration() { ui->touch_calibration_config->setEnabled(false); ui->touch_calibration_config->setText(tr("Configuring")); - CalibrationConfigurationDialog dialog( - this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()), - static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872); + CalibrationConfigurationDialog dialog(this, ui->udp_server->text().toStdString(), + static_cast<u16>(ui->udp_port->text().toUInt()), 0, + 24872); dialog.exec(); if (dialog.completed) { min_x = dialog.min_x; @@ -269,7 +311,7 @@ void ConfigureMotionTouch::OnConfigureTouchFromButton() { bool ConfigureMotionTouch::CanCloseDialog() { if (udp_test_in_progress) { - QMessageBox::warning(this, tr("Citra"), + QMessageBox::warning(this, tr("yuzu"), tr("UDP Test or calibration configuration is in progress.<br>Please " "wait for them to finish.")); return false; @@ -282,17 +324,11 @@ void ConfigureMotionTouch::ApplyConfiguration() { return; } - std::string motion_engine = ui->motion_provider->currentData().toString().toStdString(); std::string touch_engine = ui->touch_provider->currentData().toString().toStdString(); - Common::ParamPackage motion_param{}, touch_param{}; - motion_param.Set("engine", std::move(motion_engine)); + Common::ParamPackage touch_param{}; touch_param.Set("engine", std::move(touch_engine)); - if (motion_engine == "motion_emu") { - motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value())); - } - if (touch_engine == "cemuhookudp") { touch_param.Set("min_x", min_x); touch_param.Set("min_y", min_y); @@ -300,15 +336,25 @@ void ConfigureMotionTouch::ApplyConfiguration() { touch_param.Set("max_y", max_y); } - Settings::values.motion_device = motion_param.Serialize(); Settings::values.touch_device = touch_param.Serialize(); Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked(); Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex(); Settings::values.touch_from_button_maps = touch_from_button_maps; - Settings::values.udp_input_address = ui->udp_server->text().toStdString(); - Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt()); - Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex()); + Settings::values.udp_input_servers = GetUDPServerString(); input_subsystem->ReloadInputDevices(); accept(); } + +std::string ConfigureMotionTouch::GetUDPServerString() const { + QString input_servers; + + for (const auto& item : udp_server_list_model->stringList()) { + input_servers += item; + input_servers += QLatin1Char{','}; + } + + // Remove last comma + input_servers.chop(1); + return input_servers.toStdString(); +} diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h index 3d4b5d659..15d61e8ba 100644 --- a/src/yuzu/configuration/configure_motion_touch.h +++ b/src/yuzu/configuration/configure_motion_touch.h @@ -10,6 +10,7 @@ class QLabel; class QPushButton; +class QStringListModel; class QVBoxLayout; namespace InputCommon { @@ -62,6 +63,8 @@ public slots: void ApplyConfiguration(); private slots: + void OnUDPAddServer(); + void OnUDPDeleteServer(); void OnCemuhookUDPTest(); void OnConfigureTouchCalibration(); void OnConfigureTouchFromButton(); @@ -73,10 +76,12 @@ private: void UpdateUiDisplay(); void ConnectEvents(); bool CanCloseDialog(); + std::string GetUDPServerString() const; InputCommon::InputSubsystem* input_subsystem; std::unique_ptr<Ui::ConfigureMotionTouch> ui; + QStringListModel* udp_server_list_model; // Coordinate system of the CemuhookUDP touch provider int min_x{}; diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui index 602cf8cd8..ebca835ac 100644 --- a/src/yuzu/configuration/configure_motion_touch.ui +++ b/src/yuzu/configuration/configure_motion_touch.ui @@ -2,41 +2,30 @@ <ui version="4.0"> <class>ConfigureMotionTouch</class> <widget class="QDialog" name="ConfigureMotionTouch"> - <property name="windowTitle"> - <string>Configure Motion / Touch</string> - </property> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>500</width> - <height>450</height> + <height>482</height> </rect> </property> + <property name="windowTitle"> + <string>Configure Motion / Touch</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> <layout class="QVBoxLayout"> <item> <widget class="QGroupBox" name="motion_group_box"> <property name="title"> - <string>Motion</string> + <string>Mouse Motion</string> </property> <layout class="QVBoxLayout"> <item> <layout class="QHBoxLayout"> <item> - <widget class="QLabel" name="motion_provider_label"> - <property name="text"> - <string>Motion Provider:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="motion_provider"/> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout"> - <item> <widget class="QLabel" name="motion_sensitivity_label"> <property name="text"> <string>Sensitivity:</string> @@ -180,103 +169,171 @@ </widget> </item> <item> - <layout class="QHBoxLayout"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QLabel" name="udp_server_label"> - <property name="text"> - <string>Server:</string> - </property> - </widget> + <widget class="QListView" name="udp_server_list"/> </item> <item> - <widget class="QLineEdit" name="udp_server"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel" name="udp_port_label"> - <property name="text"> - <string>Port:</string> + <property name="topMargin"> + <number>0</number> </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="udp_port"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <property name="rightMargin"> + <number>0</number> </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel" name="udp_pad_index_label"> - <property name="text"> - <string>Pad:</string> + <property name="bottomMargin"> + <number>0</number> </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="udp_pad_index"> <item> - <property name="text"> - <string>Pad 1</string> - </property> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_server_label"> + <property name="text"> + <string>Server:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="udp_server"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> </item> <item> - <property name="text"> - <string>Pad 2</string> - </property> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_port_label"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="udp_port"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> </item> <item> - <property name="text"> - <string>Pad 3</string> - </property> + <layout class="QHBoxLayout"> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="udp_learn_more"> + <property name="text"> + <string>Learn More</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="udp_test"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Test</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="udp_add"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add Server</string> + </property> + </widget> + </item> + </layout> </item> <item> - <property name="text"> - <string>Pad 4</string> - </property> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> </item> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout"> - <item> - <widget class="QLabel" name="udp_learn_more"> - <property name="text"> - <string>Learn More</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="udp_test"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Test</string> - </property> - </widget> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="udp_remove"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Remove Server</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> </item> </layout> </item> @@ -314,12 +371,12 @@ <slot>ApplyConfiguration()</slot> <hints> <hint type="sourcelabel"> - <x>220</x> - <y>380</y> + <x>20</x> + <y>20</y> </hint> <hint type="destinationlabel"> - <x>220</x> - <y>200</y> + <x>20</x> + <y>20</y> </hint> </hints> </connection> 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 1e49f0787..f598513df 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -16,6 +16,7 @@ #include "common/common_paths.h" #include "common/file_util.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" @@ -29,9 +30,10 @@ 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::configuring_global = false; + Settings::SetConfiguringGlobal(false); ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -55,7 +57,7 @@ void ConfigurePerGame::ApplyConfiguration() { ui->graphicsAdvancedTab->ApplyConfiguration(); ui->audioTab->ApplyConfiguration(); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); Settings::LogSettings(); game_config->Save(); @@ -88,9 +90,11 @@ void ConfigurePerGame::LoadConfiguration() { ui->display_title_id->setText( QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper()); - FileSys::PatchManager pm{title_id}; + auto& system = Core::System::GetInstance(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); - const auto loader = Loader::GetLoader(file); + const auto loader = Loader::GetLoader(system, file); if (control.first != nullptr) { ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); 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_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 793fd8975..cdeeec01c 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -112,8 +112,10 @@ void ConfigurePerGameAddons::LoadConfiguration() { return; } - FileSys::PatchManager pm{title_id}; - const auto loader = Loader::GetLoader(file); + auto& system = Core::System::GetInstance(); + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto loader = Loader::GetLoader(system, file); FileSys::VirtualFile update_raw; loader->ReadUpdateRaw(update_raw); diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 6334c4c50..13d9a4757 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -180,7 +180,7 @@ void ConfigureProfileManager::ApplyConfiguration() { return; } - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } void ConfigureProfileManager::SelectUser(const QModelIndex& index) { diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 9ad43ed8f..6cf2032da 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -12,6 +12,7 @@ #include "common/assert.h" #include "common/file_util.h" #include "core/core.h" +#include "core/hle/service/time/time.h" #include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configuration_shared.h" @@ -36,8 +37,8 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: } }); - ui->label_console_id->setVisible(Settings::configuring_global); - ui->button_regenerate_console_id->setVisible(Settings::configuring_global); + ui->label_console_id->setVisible(Settings::IsConfiguringGlobal()); + ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal()); SetupPerGameUI(); @@ -77,7 +78,7 @@ void ConfigureSystem::SetConfiguration() { Settings::values.rng_seed.UsingGlobal()); ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue()); ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue()); @@ -104,11 +105,29 @@ void ConfigureSystem::SetConfiguration() { void ConfigureSystem::ReadSystemSettings() {} void ConfigureSystem::ApplyConfiguration() { + auto& system = Core::System::GetInstance(); + + // Allow setting custom RTC even if system is powered on, + // to allow in-game time to be fast forwarded + if (Settings::values.custom_rtc.UsingGlobal()) { + if (ui->custom_rtc_checkbox->isChecked()) { + Settings::values.custom_rtc.SetValue( + std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); + if (system.IsPoweredOn()) { + const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() + + Service::Time::TimeManager::GetExternalTimeZoneOffset()}; + system.GetTimeManager().UpdateLocalSystemClockTime(posix_time); + } + } else { + Settings::values.custom_rtc.SetValue(std::nullopt); + } + } + if (!enabled) { return; } - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { // Guard if during game and set to game-specific value if (Settings::values.language_index.UsingGlobal()) { Settings::values.language_index.SetValue(ui->combo_language->currentIndex()); @@ -131,15 +150,6 @@ void ConfigureSystem::ApplyConfiguration() { Settings::values.rng_seed.SetValue(std::nullopt); } } - - if (Settings::values.custom_rtc.UsingGlobal()) { - if (ui->custom_rtc_checkbox->isChecked()) { - Settings::values.custom_rtc.SetValue( - std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch())); - } else { - Settings::values.custom_rtc.SetValue(std::nullopt); - } - } } else { ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index, ui->combo_language); @@ -189,7 +199,7 @@ void ConfigureSystem::ApplyConfiguration() { } } - Settings::Apply(); + Settings::Apply(system); } void ConfigureSystem::RefreshConsoleID() { @@ -210,7 +220,7 @@ void ConfigureSystem::RefreshConsoleID() { } void ConfigureSystem::SetupPerGameUI() { - if (Settings::configuring_global) { + if (Settings::IsConfiguringGlobal()) { ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal()); ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal()); ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal()); 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_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index dbe3f78c8..aed876008 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -9,6 +9,7 @@ #include <QDirIterator> #include "common/common_types.h" #include "common/file_util.h" +#include "core/core.h" #include "core/settings.h" #include "ui_configure_ui.h" #include "yuzu/configuration/configure_ui.h" @@ -84,7 +85,7 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.enable_screenshot_save_as = ui->enable_screenshot_save_as->isChecked(); Common::FS::GetUserPath(Common::FS::UserPath::ScreenshotsDir, ui->screenshot_path_edit->text().toStdString()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } void ConfigureUi::RequestGameListUpdate() { 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/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 0e26f765b..efdc6aa50 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -48,7 +48,7 @@ private: MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { setObjectName(QStringLiteral("MicroProfile")); - setWindowTitle(tr("MicroProfile")); + setWindowTitle(tr("&MicroProfile")); resize(1000, 600); // Remove the "?" button from the titlebar and enable the maximize button setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 3439cb333..0925c10b4 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -13,10 +13,10 @@ #include "core/arm/arm_interface.h" #include "core/core.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/thread.h" #include "core/memory.h" @@ -25,7 +25,6 @@ namespace { constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{ {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green}, - {Qt::GlobalColor::darkGreen, Qt::GlobalColor::green}, {Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan}, {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, {Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray}, @@ -102,7 +101,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() }; const auto& system = Core::System::GetInstance(); - add_threads(system.GlobalScheduler().GetThreadList()); + add_threads(system.GlobalSchedulerContext().GetThreadList()); return item_list; } @@ -239,9 +238,6 @@ QString WaitTreeThread::GetText() const { const auto& thread = static_cast<const Kernel::Thread&>(object); QString status; switch (thread.GetStatus()) { - case Kernel::ThreadStatus::Running: - status = tr("running"); - break; case Kernel::ThreadStatus::Ready: if (!thread.IsPaused()) { if (thread.WasRunning()) { @@ -298,34 +294,32 @@ QColor WaitTreeThread::GetColor() const { const auto& thread = static_cast<const Kernel::Thread&>(object); switch (thread.GetStatus()) { - case Kernel::ThreadStatus::Running: - return QColor(WaitTreeColors[0][color_index]); case Kernel::ThreadStatus::Ready: if (!thread.IsPaused()) { if (thread.WasRunning()) { - return QColor(WaitTreeColors[1][color_index]); + return QColor(WaitTreeColors[0][color_index]); } else { - return QColor(WaitTreeColors[2][color_index]); + return QColor(WaitTreeColors[1][color_index]); } } else { - return QColor(WaitTreeColors[3][color_index]); + return QColor(WaitTreeColors[2][color_index]); } case Kernel::ThreadStatus::Paused: - return QColor(WaitTreeColors[4][color_index]); + return QColor(WaitTreeColors[3][color_index]); case Kernel::ThreadStatus::WaitHLEEvent: case Kernel::ThreadStatus::WaitIPC: - return QColor(WaitTreeColors[5][color_index]); + return QColor(WaitTreeColors[4][color_index]); case Kernel::ThreadStatus::WaitSleep: - return QColor(WaitTreeColors[6][color_index]); + return QColor(WaitTreeColors[5][color_index]); case Kernel::ThreadStatus::WaitSynch: case Kernel::ThreadStatus::WaitMutex: case Kernel::ThreadStatus::WaitCondVar: case Kernel::ThreadStatus::WaitArb: - return QColor(WaitTreeColors[7][color_index]); + return QColor(WaitTreeColors[6][color_index]); case Kernel::ThreadStatus::Dormant: - return QColor(WaitTreeColors[8][color_index]); + return QColor(WaitTreeColors[7][color_index]); case Kernel::ThreadStatus::Dead: - return QColor(WaitTreeColors[9][color_index]); + return QColor(WaitTreeColors[8][color_index]); default: return WaitTreeItem::GetColor(); } @@ -355,14 +349,14 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); list.push_back( std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore()))); - list.push_back( - std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask()))); + list.push_back(std::make_unique<WaitTreeText>( + tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask()))); list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID()))); list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") .arg(thread.GetPriority()) .arg(thread.GetNominalPriority()))); list.push_back(std::make_unique<WaitTreeText>( - tr("last running ticks = %1").arg(thread.GetLastRunningTicks()))); + tr("last running ticks = %1").arg(thread.GetLastScheduledTick()))); const VAddr mutex_wait_address = thread.GetMutexWaitAddress(); if (mutex_wait_address != 0) { @@ -463,7 +457,7 @@ void WaitTreeModel::InitItems() { thread_items = WaitTreeItem::MakeThreadItemList(); } -WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), parent) { +WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("&Wait Tree"), parent) { setObjectName(QStringLiteral("WaitTreeWidget")); view = new QTreeView(this); view->setHeaderHidden(true); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index a9738e298..70d865112 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -25,7 +25,8 @@ #include "yuzu/main.h" #include "yuzu/uisettings.h" -GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} +GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent) + : QObject(parent), gamelist{gamelist} {} // EventFilter in order to process systemkeys while editing the searchfield bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { @@ -116,7 +117,7 @@ void GameListSearchField::setFocus() { } GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { - auto* const key_release_eater = new KeyReleaseEater(parent); + auto* const key_release_eater = new KeyReleaseEater(parent, this); layout_filter = new QHBoxLayout; layout_filter->setMargin(8); label_filter = new QLabel; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 92779a9c7..df935022d 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -174,7 +174,8 @@ public: } bool operator<(const QStandardItem& other) const override { - return data(CompatNumberRole) < other.data(CompatNumberRole); + return data(CompatNumberRole).value<QString>() < + other.data(CompatNumberRole).value<QString>(); } }; @@ -330,7 +331,7 @@ public: private: class KeyReleaseEater : public QObject { public: - explicit KeyReleaseEater(GameList* gamelist); + explicit KeyReleaseEater(GameList* gamelist, QObject* parent = nullptr); private: GameList* gamelist = nullptr; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index e0ce45fd9..23643aea2 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -235,12 +235,11 @@ GameListWorker::~GameListWorker() = default; void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { using namespace FileSys; - const auto& cache = - dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider()); + auto& system = Core::System::GetInstance(); + const auto& cache = dynamic_cast<ContentProviderUnion&>(system.GetContentProvider()); - std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games; - installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, - ContentRecordType::Program); + auto installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, + ContentRecordType::Program); if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) { installed_games = cache.ListEntriesFilterOrigin( @@ -254,23 +253,27 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } for (const auto& [slot, game] : installed_games) { - if (slot == ContentProviderUnionSlot::FrontendManual) + if (slot == ContentProviderUnionSlot::FrontendManual) { continue; + } const auto file = cache.GetEntryUnparsed(game.title_id, game.type); - std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); - if (!loader) + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(system, file); + if (!loader) { continue; + } std::vector<u8> icon; std::string name; u64 program_id = 0; loader->ReadProgramId(program_id); - const PatchManager patch{program_id}; + const PatchManager patch{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); - if (control != nullptr) + if (control != nullptr) { GetMetadataFromControlNCA(patch, *control, icon, name); + } emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, compatibility_list, patch), @@ -280,9 +283,11 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, GameListDir* parent_dir) { - const auto callback = [this, target, recursion, - parent_dir](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { + auto& system = Core::System::GetInstance(); + + const auto callback = [this, target, recursion, parent_dir, + &system](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { if (stop_processing) { // Breaks the callback loop. return false; @@ -293,7 +298,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read); - auto loader = Loader::GetLoader(file); + auto loader = Loader::GetLoader(system, file); if (!loader) { return true; } @@ -331,7 +336,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa std::string name = " "; [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - const FileSys::PatchManager patch{program_id}; + const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, compatibility_list, patch), diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 6a2a88dd8..2e74037d1 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" @@ -27,8 +28,6 @@ #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. @@ -50,12 +49,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> @@ -80,6 +81,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/core.h" #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -121,14 +123,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/discord_impl.h" #endif -#ifdef YUZU_USE_QT_WEB_ENGINE -#include <QWebEngineProfile> -#include <QWebEngineScript> -#include <QWebEngineScriptCollection> -#include <QWebEngineSettings> -#include <QWebEngineView> -#endif - #ifdef QT_STATICPLUGIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif @@ -145,12 +139,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; constexpr int default_mouse_timeout = 2500; -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; - /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the - * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones. + * user. This is 32-bits - if we have more than 32 callouts, we should retire and recycle old ones. */ enum class CalloutFlag : uint32_t { Telemetry = 0x1, @@ -169,7 +161,7 @@ void GMainWindow::ShowTelemetryCallout() { "<br/><br/>Would you like to share your usage data with us?"); if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { Settings::values.enable_telemetry = false; - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); } } @@ -188,6 +180,30 @@ static void InitializeLogging() { #endif } +static void RemoveCachedContents() { + const auto offline_fonts = Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + + const auto offline_manual = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_manual", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + const auto offline_legal_information = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_legal_information", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + const auto offline_system_data = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_system_data", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + + Common::FS::DeleteDirRecursively(offline_fonts); + Common::FS::DeleteDirRecursively(offline_manual); + Common::FS::DeleteDirRecursively(offline_legal_information); + Common::FS::DeleteDirRecursively(offline_system_data); +} + GMainWindow::GMainWindow() : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, @@ -256,6 +272,9 @@ GMainWindow::GMainWindow() FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); + // Remove cached contents generated during the previous session + RemoveCachedContents(); + // Gen keys if necessary OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); @@ -273,9 +292,47 @@ GMainWindow::GMainWindow() connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor); connect(ui.menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor); + MigrateConfigFiles(); + + ui.action_Fullscreen->setChecked(false); + QStringList args = QApplication::arguments(); - if (args.length() >= 2) { - BootGame(args[1]); + + if (args.size() < 2) { + return; + } + + QString game_path; + + for (int i = 1; i < args.size(); ++i) { + // Preserves drag/drop functionality + if (args.size() == 2 && !args[1].startsWith(QChar::fromLatin1('-'))) { + game_path = args[1]; + break; + } + + // Launch game in fullscreen mode + if (args[i] == QStringLiteral("-f")) { + ui.action_Fullscreen->setChecked(true); + continue; + } + + // Launch game at path + if (args[i] == QStringLiteral("-g")) { + if (i >= args.size() - 1) { + continue; + } + + if (args[i + 1].startsWith(QChar::fromLatin1('-'))) { + continue; + } + + game_path = args[++i]; + } + } + + if (!game_path.isEmpty()) { + BootGame(game_path); } } @@ -288,38 +345,34 @@ GMainWindow::~GMainWindow() { void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get()); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint); + + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint); dialog.setWindowModality(Qt::WindowModal); dialog.exec(); emit ControllerSelectorReconfigureFinished(); // Don't forget to apply settings. - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); config->Save(); UpdateStatusButtons(); } void GMainWindow::ProfileSelectorSelectProfile() { - const Service::Account::ProfileManager manager; - int index = 0; - if (manager.GetUserCount() != 1) { - QtProfileSelectionDialog dialog(this); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - - if (dialog.exec() == QDialog::Rejected) { - emit ProfileSelectorFinishedSelection(std::nullopt); - return; - } - - index = dialog.GetIndex(); + QtProfileSelectionDialog dialog(this); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + if (dialog.exec() == QDialog::Rejected) { + emit ProfileSelectorFinishedSelection(std::nullopt); + return; } - const auto uuid = manager.GetUser(static_cast<std::size_t>(index)); + const Service::Account::ProfileManager manager; + const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex())); if (!uuid.has_value()) { emit ProfileSelectorFinishedSelection(std::nullopt); return; @@ -331,8 +384,9 @@ void GMainWindow::ProfileSelectorSelectProfile() { void GMainWindow::SoftwareKeyboardGetText( const Core::Frontend::SoftwareKeyboardParameters& parameters) { QtSoftwareKeyboardDialog dialog(this, parameters); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint | + Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec() == QDialog::Rejected) { @@ -348,148 +402,141 @@ void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message emit SoftwareKeyboardFinishedCheckDialog(); } +void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { - NXInputWebEngineView web_browser_view(this); + if (disable_web_applet) { + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, + "http://localhost/"); + return; + } - // Scope to contain the QProgressDialog for initialization - { - QProgressDialog progress(this); - progress.setMinimumDuration(200); - progress.setLabelText(tr("Loading Web Applet...")); - progress.setRange(0, 4); - progress.setValue(0); - progress.show(); + QtNXWebEngineView web_browser_view(this, Core::System::GetInstance(), input_subsystem.get()); - auto future = QtConcurrent::run([this] { emit WebBrowserUnpackRomFS(); }); + ui.action_Pause->setEnabled(false); + ui.action_Restart->setEnabled(false); + ui.action_Stop->setEnabled(false); - while (!future.isFinished()) - QApplication::processEvents(); + { + QProgressDialog loading_progress(this); + loading_progress.setLabelText(tr("Loading Web Applet...")); + loading_progress.setRange(0, 3); + loading_progress.setValue(0); - progress.setValue(1); + if (is_local && !Common::FS::Exists(std::string(main_url))) { + loading_progress.show(); - // Load the special shim script to handle input and exit. - QWebEngineScript nx_shim; - nx_shim.setSourceCode(GetNXShimInjectionScript()); - nx_shim.setWorldId(QWebEngineScript::MainWorld); - nx_shim.setName(QStringLiteral("nx_inject.js")); - nx_shim.setInjectionPoint(QWebEngineScript::DocumentCreation); - nx_shim.setRunsOnSubFrames(true); - web_browser_view.page()->profile()->scripts()->insert(nx_shim); + auto future = QtConcurrent::run([this] { emit WebBrowserExtractOfflineRomFS(); }); - web_browser_view.load( - QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(filename))).toString() + - QString::fromStdString(std::string(additional_args)))); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + } - progress.setValue(2); + loading_progress.setValue(1); - render_window->hide(); - web_browser_view.setFocus(); + if (is_local) { + web_browser_view.LoadLocalWebPage(main_url, additional_args); + } else { + web_browser_view.LoadExternalWebPage(main_url, additional_args); + } + + if (render_window->IsLoadingComplete()) { + render_window->hide(); + } const auto& layout = render_window->GetFramebufferLayout(); web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / - Layout::ScreenUndocked::Width); - web_browser_view.settings()->setAttribute( - QWebEngineSettings::LocalContentCanAccessRemoteUrls, true); + static_cast<qreal>(Layout::ScreenUndocked::Width)); + web_browser_view.setFocus(); web_browser_view.show(); - progress.setValue(3); + loading_progress.setValue(2); - QApplication::processEvents(); + QCoreApplication::processEvents(); - progress.setValue(4); + loading_progress.setValue(3); } - bool finished = false; - QAction* exit_action = new QAction(tr("Exit Web Applet"), this); - connect(exit_action, &QAction::triggered, this, [&finished] { finished = true; }); - ui.menubar->addAction(exit_action); + bool exit_check = false; - auto& npad = - Core::System::GetInstance() - .ServiceManager() - .GetService<Service::HID::Hid>("hid") - ->GetAppletResource() - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad); - - const auto fire_js_keypress = [&web_browser_view](u32 key_code) { - web_browser_view.page()->runJavaScript( - QStringLiteral("document.dispatchEvent(new KeyboardEvent('keydown', {'key': %1}));") - .arg(key_code)); - }; + // TODO (Morph): Remove this + QAction* exit_action = new QAction(tr("Disable Web Applet"), this); + connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { + const auto result = QMessageBox::warning( + this, tr("Disable Web Applet"), + tr("Disabling the web applet will cause it to not be shown again for the rest of the " + "emulated session. This can lead to undefined behavior and should only be used with " + "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"), + QMessageBox::Yes | QMessageBox::No); + if (result == QMessageBox::Yes) { + disable_web_applet = true; + web_browser_view.SetFinished(true); + } + }); + ui.menubar->addAction(exit_action); - QMessageBox::information( - this, tr("Exit"), - tr("To exit the web application, use the game provided controls to select exit, select the " - "'Exit Web Applet' option in the menu bar, or press the 'Enter' key.")); - - bool running_exit_check = false; - while (!finished) { - QApplication::processEvents(); - - if (!running_exit_check) { - web_browser_view.page()->runJavaScript(QStringLiteral("applet_done;"), - [&](const QVariant& res) { - running_exit_check = false; - if (res.toBool()) - finished = true; - }); - running_exit_check = true; + while (!web_browser_view.IsFinished()) { + QCoreApplication::processEvents(); + + if (!exit_check) { + web_browser_view.page()->runJavaScript( + QStringLiteral("end_applet;"), [&](const QVariant& variant) { + exit_check = false; + if (variant.toBool()) { + web_browser_view.SetFinished(true); + web_browser_view.SetExitReason( + Service::AM::Applets::WebExitReason::EndButtonPressed); + } + }); + + exit_check = true; } - const auto input = npad.GetAndResetPressState(); - for (std::size_t i = 0; i < Settings::NativeButton::NumButtons; ++i) { - if ((input & (1 << i)) != 0) { - LOG_DEBUG(Frontend, "firing input for button id={:02X}", i); - web_browser_view.page()->runJavaScript( - QStringLiteral("yuzu_key_callbacks[%1]();").arg(i)); + if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { + if (!web_browser_view.IsFinished()) { + web_browser_view.SetFinished(true); + web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); } + + web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); } - if (input & 0x00888000) // RStick Down | LStick Down | DPad Down - fire_js_keypress(40); // Down Arrow Key - else if (input & 0x00444000) // RStick Right | LStick Right | DPad Right - fire_js_keypress(39); // Right Arrow Key - else if (input & 0x00222000) // RStick Up | LStick Up | DPad Up - fire_js_keypress(38); // Up Arrow Key - else if (input & 0x00111000) // RStick Left | LStick Left | DPad Left - fire_js_keypress(37); // Left Arrow Key - else if (input & 0x00000001) // A Button - fire_js_keypress(13); // Enter Key + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + const auto exit_reason = web_browser_view.GetExitReason(); + const auto last_url = web_browser_view.GetLastURL(); + web_browser_view.hide(); - render_window->show(); + render_window->setFocus(); - ui.menubar->removeAction(exit_action); - // Needed to update render window focus/show and remove menubar action - QApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + if (render_window->IsLoadingComplete()) { + render_window->show(); + } -#else + ui.action_Pause->setEnabled(true); + ui.action_Restart->setEnabled(true); + ui.action_Stop->setEnabled(true); -void GMainWindow::WebBrowserOpenPage(std::string_view filename, std::string_view additional_args) { - QMessageBox::warning( - this, tr("Web Applet"), - tr("This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot " - "properly display the game manual or web page requested."), - QMessageBox::Ok, QMessageBox::Ok); + ui.menubar->removeAction(exit_action); - LOG_INFO(Frontend, - "(STUBBED) called - Missing QtWebEngine dependency needed to open website page at " - "'{}' with arguments '{}'!", - filename, additional_args); + QCoreApplication::processEvents(); - emit WebBrowserFinishedBrowsing(); -} + emit WebBrowserClosed(exit_reason, last_url); + +#else + + // Utilize the same fallback as the default web browser applet. + emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); #endif +} void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING @@ -551,13 +598,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 @@ -568,11 +616,10 @@ void GMainWindow::InitializeWidgets() { if (emulation_running) { return; } - bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); + Settings::values.use_asynchronous_gpu_emulation.SetValue( + !Settings::values.use_asynchronous_gpu_emulation.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); async_status_button->setText(tr("ASYNC")); async_status_button->setCheckable(true); @@ -587,16 +634,13 @@ void GMainWindow::InitializeWidgets() { return; } Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); - bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() || - Settings::values.use_multi_core.GetValue(); - Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async); - async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); multicore_status_button->setText(tr("MULTICORE")); multicore_status_button->setCheckable(true); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); + statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, async_status_button); @@ -610,11 +654,6 @@ void GMainWindow::InitializeWidgets() { }); renderer_status_button->toggle(); -#ifndef HAS_VULKAN - renderer_status_button->setChecked(false); - renderer_status_button->setCheckable(false); - renderer_status_button->setDisabled(true); -#else renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); connect(renderer_status_button, &QPushButton::clicked, [this] { @@ -627,9 +666,8 @@ void GMainWindow::InitializeWidgets() { Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL); } - Settings::Apply(); + Settings::Apply(Core::System::GetInstance()); }); -#endif // HAS_VULKAN statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->setVisible(true); @@ -665,7 +703,7 @@ void GMainWindow::InitializeRecentFileMenuActions() { } ui.menu_recent_files->addSeparator(); QAction* action_clear_recent_files = new QAction(this); - action_clear_recent_files->setText(tr("Clear Recent Files")); + action_clear_recent_files->setText(tr("&Clear Recent Files")); connect(action_clear_recent_files, &QAction::triggered, this, [this] { UISettings::values.recent_files.clear(); UpdateRecentFiles(); @@ -796,10 +834,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, @@ -926,7 +965,10 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::OnDisplayTitleBars); connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); - connect(ui.action_Reset_Window_Size, &QAction::triggered, this, &GMainWindow::ResetWindowSize); + connect(ui.action_Reset_Window_Size_720, &QAction::triggered, this, + &GMainWindow::ResetWindowSize720); + connect(ui.action_Reset_Window_Size_1080, &QAction::triggered, this, + &GMainWindow::ResetWindowSize1080); // Fullscreen connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); @@ -974,7 +1016,7 @@ void GMainWindow::AllowOSSleep() { #endif } -bool GMainWindow::LoadROM(const QString& filename) { +bool GMainWindow::LoadROM(const QString& filename, std::size_t program_index) { // Shutdown previous session if the emu thread is still active... if (emu_thread != nullptr) ShutdownGame(); @@ -988,7 +1030,6 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetAppletFrontendSet({ std::make_unique<QtControllerSelector>(*this), // Controller Selector - nullptr, // E-Commerce std::make_unique<QtErrorDisplay>(*this), // Error Display nullptr, // Parental Controls nullptr, // Photo Viewer @@ -999,7 +1040,8 @@ bool GMainWindow::LoadROM(const QString& filename) { system.RegisterHostThread(); - const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; + const Core::System::ResultStatus result{ + system.Load(*render_window, filename.toStdString(), program_index)}; const auto drd_callout = (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; @@ -1039,20 +1081,24 @@ bool GMainWindow::LoadROM(const QString& filename) { break; default: - if (static_cast<u32>(result) > - static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { + if (result > Core::System::ResultStatus::ErrorLoader) { const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); const u16 error_id = static_cast<u16>(result) - loader_id; const std::string error_code = fmt::format("({:04X}-{:04X})", loader_id, error_id); LOG_CRITICAL(Frontend, "Failed to load ROM! {}", error_code); - QMessageBox::critical( - this, - tr("Error while loading ROM! ").append(QString::fromStdString(error_code)), - QString::fromStdString(fmt::format( - "{}<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the " - "yuzu quickstart guide</a> to redump your files.<br>You can refer " - "to the yuzu wiki</a> or the yuzu Discord</a> for help.", - static_cast<Loader::ResultStatus>(error_id)))); + + const auto title = + tr("Error while loading ROM! %1", "%1 signifies a numeric error code.") + .arg(QString::fromStdString(error_code)); + const auto description = + tr("%1<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the " + "yuzu quickstart guide</a> to redump your files.<br>You can refer " + "to the yuzu wiki</a> or the yuzu Discord</a> for help.", + "%1 signifies an error string.") + .arg(QString::fromStdString( + GetResultStatusString(static_cast<Loader::ResultStatus>(error_id)))); + + QMessageBox::critical(this, title, description); } else { QMessageBox::critical( this, tr("Error while loading ROM!"), @@ -1081,26 +1127,37 @@ void GMainWindow::SelectAndSetCurrentUser() { Settings::values.current_user = dialog.GetIndex(); } -void GMainWindow::BootGame(const QString& filename) { +void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { LOG_INFO(Frontend, "yuzu starting..."); StoreRecentFile(filename); // Put the filename on top of the list u64 title_id{0}; + last_filename_booted = filename; + + auto& system = Core::System::GetInstance(); const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); - const auto loader = Loader::GetLoader(v_file); + const auto loader = Loader::GetLoader(system, v_file, program_index); + 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(); + + // Save configurations + UpdateUISettings(); + game_list->SaveInterfaceLayout(); + config->Save(); + Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { SelectAndSetCurrentUser(); } - if (!LoadROM(filename)) + if (!LoadROM(filename, program_index)) return; // Create and start the emulation thread @@ -1108,7 +1165,12 @@ void GMainWindow::BootGame(const QString& filename) { emit EmulationStarting(emu_thread.get()); emu_thread->start(); + // Register an ExecuteProgram callback such that Core can execute a sub-program + system.RegisterExecuteProgramCallback( + [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); }); + connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); + connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues connect(emu_thread.get(), &EmuThread::DebugModeEntered, waitTreeWidget, @@ -1132,15 +1194,19 @@ void GMainWindow::BootGame(const QString& filename) { if (UISettings::values.hide_mouse) { mouse_hide_timer.start(); - setMouseTracking(true); - ui.centralwidget->setMouseTracking(true); + render_window->installEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, true); } std::string title_name; std::string title_version; - const auto res = Core::System::GetInstance().GetGameName(title_name); + const auto res = system.GetGameName(title_name); - const auto metadata = FileSys::PatchManager(title_id).GetControlMetadata(); + const auto metadata = [&system, title_id] { + const FileSys::PatchManager pm(title_id, system.GetFileSystemController(), + system.GetContentProvider()); + return pm.GetControlMetadata(); + }(); if (metadata.first != nullptr) { title_version = metadata.first->GetVersionString(); title_name = metadata.first->GetApplicationName(); @@ -1151,7 +1217,7 @@ void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); UpdateWindowTitle(title_name, title_version); - loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); + loading_screen->Prepare(system.GetAppLoader()); loading_screen->show(); emulation_running = true; @@ -1206,8 +1272,8 @@ void GMainWindow::ShutdownGame() { } game_list->SetFilterFocus(); - setMouseTracking(false); - ui.centralwidget->setMouseTracking(false); + render_window->removeEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, false); UpdateWindowTitle(); @@ -1219,9 +1285,7 @@ void GMainWindow::ShutdownGame() { emu_frametime_label->setVisible(false); async_status_button->setEnabled(true); multicore_status_button->setEnabled(true); -#ifdef HAS_VULKAN renderer_status_button->setEnabled(true); -#endif emulation_running = false; @@ -1270,16 +1334,18 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target const std::string& game_path) { std::string path; QString open_target; + auto& system = Core::System::GetInstance(); - const auto [user_save_size, device_save_size] = [this, &program_id, &game_path] { - FileSys::PatchManager pm{program_id}; + const auto [user_save_size, device_save_size] = [this, &game_path, &program_id, &system] { + const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata().first; if (control != nullptr) { return std::make_pair(control->GetDefaultNormalSaveSize(), control->GetDeviceSaveDataSize()); } else { const auto file = Core::GetGameFileFromPath(vfs, game_path); - const auto loader = Loader::GetLoader(file); + const auto loader = Loader::GetLoader(system, file); FileSys::NACP nacp{}; loader->ReadControlData(nacp); @@ -1321,12 +1387,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target const auto user_id = manager.GetUser(static_cast<std::size_t>(index)); ASSERT(user_id); path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - FileSys::SaveDataSpaceId::NandUser, + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, program_id, user_id->uuid, 0); } else { // Device save data path = nand_dir + FileSys::SaveDataFactory::GetFullPath( - FileSys::SaveDataSpaceId::NandUser, + system, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, program_id, {}, 0); } @@ -1506,7 +1572,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); for (const auto& entry : dlc_entries) { - if ((entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id) { + if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(entry.title_id) || fs_controller.GetSDMCContents()->RemoveExistingEntry(entry.title_id); @@ -1581,7 +1647,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"), @@ -1605,7 +1672,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa "cancelled the operation.")); }; - const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); + auto& system = Core::System::GetInstance(); + const auto loader = Loader::GetLoader(system, vfs->OpenFile(game_path, FileSys::Mode::Read)); if (loader == nullptr) { failed(); return; @@ -1617,7 +1685,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto& installed = Core::System::GetInstance().GetContentProvider(); + const auto& installed = system.GetContentProvider(); const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id); if (!romfs_title_id) { @@ -1632,7 +1700,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (*romfs_title_id == program_id) { const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); - FileSys::PatchManager pm{program_id}; + const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), installed}; romfs = pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program); } else { romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); @@ -1749,7 +1817,8 @@ void GMainWindow::OnGameListShowList(bool show) { void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { u64 title_id{}; const auto v_file = Core::GetGameFileFromPath(vfs, file); - const auto loader = Loader::GetLoader(v_file); + const auto loader = Loader::GetLoader(Core::System::GetInstance(), v_file); + if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { QMessageBox::information(this, tr("Properties"), tr("The game properties could not be loaded.")); @@ -2077,11 +2146,12 @@ void GMainWindow::OnStartGame() { qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); qRegisterMetaType<std::string_view>("std::string_view"); + qRegisterMetaType<Service::AM::Applets::WebExitReason>("Service::AM::Applets::WebExitReason"); connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); @@ -2106,14 +2176,14 @@ void GMainWindow::OnPauseGame() { } void GMainWindow::OnStopGame() { - Core::System& system{Core::System::GetInstance()}; + auto& system{Core::System::GetInstance()}; if (system.GetExitLock() && !ConfirmForceLockedExit()) { return; } ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(system.IsPoweredOn()); UpdateStatusButtons(); } @@ -2121,6 +2191,11 @@ void GMainWindow::OnLoadComplete() { loading_screen->OnLoadComplete(); } +void GMainWindow::OnExecuteProgram(std::size_t program_index) { + ShutdownGame(); + BootGame(last_filename_booted, program_index); +} + void GMainWindow::ErrorDisplayDisplayError(QString body) { QMessageBox::critical(this, tr("Error Display"), body); emit ErrorDisplayFinished(); @@ -2220,7 +2295,7 @@ void GMainWindow::ToggleWindowMode() { } } -void GMainWindow::ResetWindowSize() { +void GMainWindow::ResetWindowSize720() { const auto aspect_ratio = Layout::EmulationAspectRatio( static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); @@ -2234,6 +2309,20 @@ void GMainWindow::ResetWindowSize() { } } +void GMainWindow::ResetWindowSize1080() { + const auto aspect_ratio = Layout::EmulationAspectRatio( + static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()), + static_cast<float>(Layout::ScreenDocked::Height) / Layout::ScreenDocked::Width); + if (!ui.action_Single_Window_Mode->isChecked()) { + render_window->resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height); + } else { + resize(Layout::ScreenDocked::Height / aspect_ratio, + Layout::ScreenDocked::Height + menuBar()->height() + + (ui.action_Show_Status_Bar->isChecked() ? statusBar()->height() : 0)); + } +} + void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence; @@ -2265,12 +2354,12 @@ void GMainWindow::OnConfigure() { config->Save(); if (UISettings::values.hide_mouse && emulation_running) { - setMouseTracking(true); - ui.centralwidget->setMouseTracking(true); + render_window->installEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, true); mouse_hide_timer.start(); } else { - setMouseTracking(false); - ui.centralwidget->setMouseTracking(false); + render_window->removeEventFilter(render_window); + render_window->setAttribute(Qt::WA_Hover, false); } UpdateStatusButtons(); @@ -2283,10 +2372,11 @@ void GMainWindow::OnConfigurePerGame() { void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { const auto v_file = Core::GetGameFileFromPath(vfs, file_name); + const auto& system = Core::System::GetInstance(); ConfigurePerGame dialog(this, title_id); dialog.LoadFromFile(v_file); - auto result = dialog.exec(); + const auto result = dialog.exec(); if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); @@ -2296,13 +2386,14 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } // Do not cause the global config to write local settings into the config file - Settings::RestoreGlobalState(); + const bool is_powered_on = system.IsPoweredOn(); + Settings::RestoreGlobalState(is_powered_on); - if (!Core::System::GetInstance().IsPoweredOn()) { + if (!is_powered_on) { config->Save(); } } else { - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(system.IsPoweredOn()); } } @@ -2397,6 +2488,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); @@ -2454,16 +2568,29 @@ 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() || - Settings::values.use_multi_core.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); -#ifdef HAS_VULKAN renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); +} + +void GMainWindow::UpdateUISettings() { + if (!ui.action_Fullscreen->isChecked()) { + UISettings::values.geometry = saveGeometry(); + UISettings::values.renderwindow_geometry = render_window->saveGeometry(); + } + UISettings::values.state = saveState(); +#if MICROPROFILE_ENABLED + UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); + UISettings::values.microprofile_visible = microProfileDialog->isVisible(); #endif + UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); + UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); + UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); + UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); + UISettings::values.first_start = false; } void GMainWindow::HideMouseCursor() { @@ -2472,21 +2599,17 @@ void GMainWindow::HideMouseCursor() { ShowMouseCursor(); return; } - setCursor(QCursor(Qt::BlankCursor)); + render_window->setCursor(QCursor(Qt::BlankCursor)); } void GMainWindow::ShowMouseCursor() { - unsetCursor(); + render_window->unsetCursor(); if (emu_thread != nullptr && UISettings::values.hide_mouse) { mouse_hide_timer.start(); } } -void GMainWindow::mouseMoveEvent(QMouseEvent* event) { - ShowMouseCursor(); -} - -void GMainWindow::mousePressEvent(QMouseEvent* event) { +void GMainWindow::OnMouseActivity() { ShowMouseCursor(); } @@ -2550,7 +2673,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det if (emu_thread) { ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn()); UpdateStatusButtons(); } } else { @@ -2653,7 +2776,7 @@ std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProv dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), [&program_id, &installed](const FileSys::ContentProviderEntry& entry) { - return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && + return FileSys::GetBaseTitleID(entry.title_id) == program_id && installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; }); @@ -2699,22 +2822,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { return; } - if (!ui.action_Fullscreen->isChecked()) { - UISettings::values.geometry = saveGeometry(); - UISettings::values.renderwindow_geometry = render_window->saveGeometry(); - } - UISettings::values.state = saveState(); -#if MICROPROFILE_ENABLED - UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry(); - UISettings::values.microprofile_visible = microProfileDialog->isVisible(); -#endif - UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); - UISettings::values.fullscreen = ui.action_Fullscreen->isChecked(); - UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); - UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); - UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); - UISettings::values.first_start = false; - + UpdateUISettings(); game_list->SaveInterfaceLayout(); hotkey_registry.SaveHotkeys(); @@ -2722,7 +2830,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { if (emu_thread != nullptr) { ShutdownGame(); - Settings::RestoreGlobalState(); + Settings::RestoreGlobalState(Core::System::GetInstance().IsPoweredOn()); UpdateStatusButtons(); } @@ -2890,7 +2998,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { UpdateWindowTitle(); if (emulation_running) - ui.action_Start->setText(tr("Continue")); + ui.action_Start->setText(tr("&Continue")); } void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index afcfa68a9..31788ea62 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -55,6 +55,10 @@ namespace InputCommon { class InputSubsystem; } +namespace Service::AM::Applets { +enum class WebExitReason : u32; +} + enum class EmulatedDirectoryTarget { NAND, SDMC, @@ -126,18 +130,20 @@ signals: void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); void SoftwareKeyboardFinishedCheckDialog(); - void WebBrowserUnpackRomFS(); - void WebBrowserFinishedBrowsing(); + void WebBrowserExtractOfflineRomFS(); + void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); public slots: void OnLoadComplete(); + void OnExecuteProgram(std::size_t program_index); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); void ErrorDisplayDisplayError(QString body); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); - void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); + void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, + bool is_local); void OnAppFocusStateChanged(Qt::ApplicationState state); private: @@ -154,8 +160,8 @@ private: void PreventOSSleep(); void AllowOSSleep(); - bool LoadROM(const QString& filename); - void BootGame(const QString& filename); + bool LoadROM(const QString& filename, std::size_t program_index); + void BootGame(const QString& filename, std::size_t program_index = 0); void ShutdownGame(); void ShowTelemetryCallout(); @@ -236,11 +242,13 @@ private slots: void ShowFullscreen(); void HideFullscreen(); void ToggleWindowMode(); - void ResetWindowSize(); + void ResetWindowSize720(); + void ResetWindowSize1080(); void OnCaptureScreenshot(); void OnCoreError(Core::System::ResultStatus, std::string); void OnReinitializeKeys(ReinitializeKeyBehavior behavior); void OnLanguageChanged(const QString& locale); + void OnMouseActivity(); private: void RemoveBaseContent(u64 program_id, const QString& entry_type); @@ -251,10 +259,12 @@ 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(); void UpdateStatusButtons(); + void UpdateUISettings(); void HideMouseCursor(); void ShowMouseCursor(); void OpenURL(const QUrl& url); @@ -316,10 +326,14 @@ private: // Install progress dialog QProgressDialog* install_progress; + // Last game booted, used for multi-process apps + QString last_filename_booted; + + // Disables the web applet for the rest of the emulated session + bool disable_web_applet{}; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; }; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 2f3792247..e2ad5baf6 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -25,16 +25,7 @@ </property> <widget class="QWidget" name="centralwidget"> <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> + <property name="margin"> <number>0</number> </property> </layout> @@ -45,7 +36,7 @@ <x>0</x> <y>0</y> <width>1280</width> - <height>21</height> + <height>26</height> </rect> </property> <widget class="QMenu" name="menu_File"> @@ -54,7 +45,7 @@ </property> <widget class="QMenu" name="menu_recent_files"> <property name="title"> - <string>Recent Files</string> + <string>&Recent Files</string> </property> </widget> <addaction name="action_Install_File_NAND"/> @@ -89,7 +80,7 @@ </property> <widget class="QMenu" name="menu_View_Debugging"> <property name="title"> - <string>Debugging</string> + <string>&Debugging</string> </property> </widget> <addaction name="action_Fullscreen"/> @@ -97,13 +88,14 @@ <addaction name="action_Display_Dock_Widget_Headers"/> <addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Status_Bar"/> - <addaction name="action_Reset_Window_Size"/> + <addaction name="action_Reset_Window_Size_720"/> + <addaction name="action_Reset_Window_Size_1080"/> <addaction name="separator"/> <addaction name="menu_View_Debugging"/> </widget> <widget class="QMenu" name="menu_Tools"> <property name="title"> - <string>Tools</string> + <string>&Tools</string> </property> <addaction name="action_Rederive"/> <addaction name="separator"/> @@ -131,17 +123,17 @@ <bool>true</bool> </property> <property name="text"> - <string>Install Files to NAND...</string> + <string>&Install Files to NAND...</string> </property> </action> <action name="action_Load_File"> <property name="text"> - <string>Load File...</string> + <string>L&oad File...</string> </property> </action> <action name="action_Load_Folder"> <property name="text"> - <string>Load Folder...</string> + <string>Load &Folder...</string> </property> </action> <action name="action_Exit"> @@ -175,12 +167,12 @@ </action> <action name="action_Rederive"> <property name="text"> - <string>Reinitialize keys...</string> + <string>&Reinitialize keys...</string> </property> </action> <action name="action_About"> <property name="text"> - <string>About yuzu</string> + <string>&About yuzu</string> </property> </action> <action name="action_Single_Window_Mode"> @@ -188,12 +180,12 @@ <bool>true</bool> </property> <property name="text"> - <string>Single Window Mode</string> + <string>Single &Window Mode</string> </property> </action> <action name="action_Configure"> <property name="text"> - <string>Configure...</string> + <string>Con&figure...</string> </property> </action> <action name="action_Display_Dock_Widget_Headers"> @@ -201,7 +193,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Display Dock Widget Headers</string> + <string>Display D&ock Widget Headers</string> </property> </action> <action name="action_Show_Filter_Bar"> @@ -209,7 +201,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Show Filter Bar</string> + <string>Show &Filter Bar</string> </property> </action> <action name="action_Show_Status_Bar"> @@ -217,12 +209,26 @@ <bool>true</bool> </property> <property name="text"> + <string>Show &Status Bar</string> + </property> + <property name="iconText"> <string>Show Status Bar</string> </property> </action> - <action name="action_Reset_Window_Size"> + <action name="action_Reset_Window_Size_720"> + <property name="text"> + <string>Reset Window Size to &720p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 720p</string> + </property> + </action> + <action name="action_Reset_Window_Size_1080"> <property name="text"> - <string>Reset Window Size</string> + <string>Reset Window Size to &1080p</string> + </property> + <property name="iconText"> + <string>Reset Window Size to 1080p</string> </property> </action> <action name="action_Fullscreen"> @@ -230,7 +236,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Fullscreen</string> + <string>F&ullscreen</string> </property> </action> <action name="action_Restart"> @@ -238,7 +244,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Restart</string> + <string>&Restart</string> </property> </action> <action name="action_Load_Amiibo"> @@ -246,7 +252,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Load Amiibo...</string> + <string>Load &Amiibo...</string> </property> </action> <action name="action_Report_Compatibility"> @@ -254,7 +260,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Report Compatibility</string> + <string>&Report Compatibility</string> </property> <property name="visible"> <bool>false</bool> @@ -262,22 +268,22 @@ </action> <action name="action_Open_Mods_Page"> <property name="text"> - <string>Open Mods Page</string> + <string>Open &Mods Page</string> </property> </action> <action name="action_Open_Quickstart_Guide"> <property name="text"> - <string>Open Quickstart Guide</string> + <string>Open &Quickstart Guide</string> </property> </action> <action name="action_Open_FAQ"> <property name="text"> - <string>FAQ</string> + <string>&FAQ</string> </property> </action> <action name="action_Open_yuzu_Folder"> <property name="text"> - <string>Open yuzu Folder</string> + <string>Open &yuzu Folder</string> </property> </action> <action name="action_Capture_Screenshot"> @@ -285,7 +291,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Capture Screenshot</string> + <string>&Capture Screenshot</string> </property> </action> <action name="action_Configure_Current_Game"> @@ -293,7 +299,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Configure Current Game...</string> + <string>Configure C&urrent Game...</string> </property> </action> </widget> diff --git a/src/yuzu/util/url_request_interceptor.cpp b/src/yuzu/util/url_request_interceptor.cpp new file mode 100644 index 000000000..b637e771e --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.cpp @@ -0,0 +1,34 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include "yuzu/util/url_request_interceptor.h" + +UrlRequestInterceptor::UrlRequestInterceptor(QObject* p) : QWebEngineUrlRequestInterceptor(p) {} + +UrlRequestInterceptor::~UrlRequestInterceptor() = default; + +void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + const auto resource_type = info.resourceType(); + + switch (resource_type) { + case QWebEngineUrlRequestInfo::ResourceTypeMainFrame: + requested_url = info.requestUrl(); + emit FrameChanged(); + break; + case QWebEngineUrlRequestInfo::ResourceTypeSubFrame: + case QWebEngineUrlRequestInfo::ResourceTypeXhr: + emit FrameChanged(); + break; + default: + break; + } +} + +QUrl UrlRequestInterceptor::GetRequestedURL() const { + return requested_url; +} + +#endif diff --git a/src/yuzu/util/url_request_interceptor.h b/src/yuzu/util/url_request_interceptor.h new file mode 100644 index 000000000..8a7f7499f --- /dev/null +++ b/src/yuzu/util/url_request_interceptor.h @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#ifdef YUZU_USE_QT_WEB_ENGINE + +#include <QObject> +#include <QWebEngineUrlRequestInterceptor> + +class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor { + Q_OBJECT + +public: + explicit UrlRequestInterceptor(QObject* p = nullptr); + ~UrlRequestInterceptor() override; + + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + + QUrl GetRequestedURL() const; + +signals: + void FrameChanged(); + +private: + QUrl requested_url; +}; + +#endif |
