summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt25
-rw-r--r--src/yuzu/aboutdialog.ui20
-rw-r--r--src/yuzu/applets/controller.cpp297
-rw-r--r--src/yuzu/applets/controller.h44
-rw-r--r--src/yuzu/applets/controller.ui49
-rw-r--r--src/yuzu/applets/error.cpp12
-rw-r--r--src/yuzu/applets/profile_select.cpp13
-rw-r--r--src/yuzu/applets/profile_select.h3
-rw-r--r--src/yuzu/applets/software_keyboard.cpp4
-rw-r--r--src/yuzu/applets/software_keyboard.h2
-rw-r--r--src/yuzu/applets/web_browser.cpp443
-rw-r--r--src/yuzu/applets/web_browser.h191
-rw-r--r--src/yuzu/applets/web_browser_scripts.h193
-rw-r--r--src/yuzu/bootmanager.cpp93
-rw-r--r--src/yuzu/bootmanager.h13
-rw-r--r--src/yuzu/compatdb.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp388
-rw-r--r--src/yuzu/configuration/config.h22
-rw-r--r--src/yuzu/configuration/configure.ui20
-rw-r--r--src/yuzu/configuration/configure_audio.cpp10
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp3
-rw-r--r--src/yuzu/configuration/configure_cpu.ui12
-rw-r--r--src/yuzu/configuration/configure_debug.cpp7
-rw-r--r--src/yuzu/configuration/configure_debug.ui120
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp7
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h9
-rw-r--r--src/yuzu/configuration/configure_debug_controller.ui20
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_general.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp39
-rw-r--r--src/yuzu/configuration/configure_graphics.h1
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_input.cpp89
-rw-r--r--src/yuzu/configuration/configure_input.h14
-rw-r--r--src/yuzu/configuration/configure_input.ui46
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp16
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui223
-rw-r--r--src/yuzu/configuration/configure_input_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_dialog.h38
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp831
-rw-r--r--src/yuzu/configuration/configure_input_player.h83
-rw-r--r--src/yuzu/configuration/configure_input_player.ui241
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.h40
-rw-r--r--src/yuzu/configuration/configure_input_profile_dialog.ui (renamed from src/yuzu/configuration/configure_input_dialog.ui)24
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp136
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h5
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui267
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.ui46
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp14
-rw-r--r--src/yuzu/configuration/configure_per_game.ui20
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp6
-rw-r--r--src/yuzu/configuration/configure_profile_manager.cpp2
-rw-r--r--src/yuzu/configuration/configure_system.cpp40
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui10
-rw-r--r--src/yuzu/configuration/configure_touchscreen_advanced.ui22
-rw-r--r--src/yuzu/configuration/configure_ui.cpp3
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp146
-rw-r--r--src/yuzu/configuration/configure_vibration.h43
-rw-r--r--src/yuzu/configuration/configure_vibration.ui546
-rw-r--r--src/yuzu/configuration/input_profiles.cpp131
-rw-r--r--src/yuzu/configuration/input_profiles.h32
-rw-r--r--src/yuzu/debugger/profiler.cpp2
-rw-r--r--src/yuzu/debugger/wait_tree.cpp36
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/game_list_p.h5
-rw-r--r--src/yuzu/game_list_worker.cpp36
-rw-r--r--src/yuzu/main.cpp598
-rw-r--r--src/yuzu/main.h30
-rw-r--r--src/yuzu/main.ui78
-rw-r--r--src/yuzu/util/url_request_interceptor.cpp34
-rw-r--r--src/yuzu/util/url_request_interceptor.h30
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>
+ &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
+ </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>&amp;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>&amp;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>&amp;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>&amp;Install Files to NAND...</string>
</property>
</action>
<action name="action_Load_File">
<property name="text">
- <string>Load File...</string>
+ <string>L&amp;oad File...</string>
</property>
</action>
<action name="action_Load_Folder">
<property name="text">
- <string>Load Folder...</string>
+ <string>Load &amp;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>&amp;Reinitialize keys...</string>
</property>
</action>
<action name="action_About">
<property name="text">
- <string>About yuzu</string>
+ <string>&amp;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 &amp;Window Mode</string>
</property>
</action>
<action name="action_Configure">
<property name="text">
- <string>Configure...</string>
+ <string>Con&amp;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&amp;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 &amp;Filter Bar</string>
</property>
</action>
<action name="action_Show_Status_Bar">
@@ -217,12 +209,26 @@
<bool>true</bool>
</property>
<property name="text">
+ <string>Show &amp;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 &amp;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 &amp;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&amp;ullscreen</string>
</property>
</action>
<action name="action_Restart">
@@ -238,7 +244,7 @@
<bool>false</bool>
</property>
<property name="text">
- <string>Restart</string>
+ <string>&amp;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 &amp;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>&amp;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 &amp;Mods Page</string>
</property>
</action>
<action name="action_Open_Quickstart_Guide">
<property name="text">
- <string>Open Quickstart Guide</string>
+ <string>Open &amp;Quickstart Guide</string>
</property>
</action>
<action name="action_Open_FAQ">
<property name="text">
- <string>FAQ</string>
+ <string>&amp;FAQ</string>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
- <string>Open yuzu Folder</string>
+ <string>Open &amp;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>&amp;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&amp;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