diff options
Diffstat (limited to 'src/yuzu')
100 files changed, 1124 insertions, 412 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 66873143e..29d506c47 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2018 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) @@ -205,6 +208,16 @@ add_executable(yuzu yuzu.rc ) +if (WIN32 AND YUZU_CRASH_DUMPS) + target_sources(yuzu PRIVATE + mini_dump.cpp + mini_dump.h + ) + + target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) + target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) +endif() + file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) @@ -218,6 +231,9 @@ if (ENABLE_QT_TRANSLATION) # Update source TS file if enabled if (GENERATE_QT_TRANSLATION) get_target_property(SRCS yuzu SOURCES) + # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals + # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm + set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations") qt_create_translation(QM_FILES ${SRCS} ${UIS} @@ -226,7 +242,13 @@ if (ENABLE_QT_TRANSLATION) -source-language en_US -target-language en_US ) - add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts) + + # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts + set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts) + set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals") + qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) + + add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE}) endif() # Find all TS files except en.ts @@ -236,6 +258,9 @@ if (ENABLE_QT_TRANSLATION) # Compile TS files to QM files qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + # Compile english plurals TS file to en.qm + qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts) + # Build a QRC file from the QM file list set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n") diff --git a/src/yuzu/Info.plist b/src/yuzu/Info.plist index 5f1c95d54..0eb377926 100644 --- a/src/yuzu/Info.plist +++ b/src/yuzu/Info.plist @@ -1,4 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> + +<!-- +SPDX-FileCopyrightText: 2015 Pierre de La Morinerie <kemenaran@gmail.com> +SPDX-License-Identifier: GPL-2.0-or-later +--> + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index 1dd7b74bf..aea82809d 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>616</width> - <height>261</height> + <height>294</height> </rect> </property> <property name="windowTitle"> @@ -127,7 +127,7 @@ p, li { white-space: pre-wrap; } <item> <widget class="QLabel" name="labelLinks"> <property name="text"> - <string><html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string> + <string><html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string> </property> <property name="openExternalLinks"> <bool>true</bool> @@ -165,6 +165,7 @@ p, li { white-space: pre-wrap; } </widget> <resources> <include location="../../dist/qt_themes_default/default/default.qrc"/> + <include location="../../dist/qt_themes/default/default.qrc"/> </resources> <connections> <connection> diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index 8be311fcb..1d8072243 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, - input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} { + input_profiles(std::make_unique<InputProfiles>()), system{system_} { ui->setupUi(this); player_widgets = { diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp index 826c6c224..c8bcfb223 100644 --- a/src/yuzu/applets/qt_profile_select.cpp +++ b/src/yuzu/applets/qt_profile_select.cpp @@ -100,6 +100,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, } QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); QCoreApplication::postEvent(tree_view, event); + SelectUser(tree_view->currentIndex()); }); const auto& profiles = profile_manager->GetAllUsers(); diff --git a/src/yuzu/applets/qt_software_keyboard.cpp b/src/yuzu/applets/qt_software_keyboard.cpp index e8b217d90..e60506197 100644 --- a/src/yuzu/applets/qt_software_keyboard.cpp +++ b/src/yuzu/applets/qt_software_keyboard.cpp @@ -213,9 +213,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( ui->button_ok_num, }, { - nullptr, + ui->button_left_optional_num, ui->button_0_num, - nullptr, + ui->button_right_optional_num, ui->button_ok_num, }, }}; @@ -330,7 +330,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( ui->button_7_num, ui->button_8_num, ui->button_9_num, + ui->button_left_optional_num, ui->button_0_num, + ui->button_right_optional_num, }; SetupMouseHover(); @@ -342,6 +344,9 @@ QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + ui->button_left_optional_num->setText(QChar{initialize_parameters.left_optional_symbol_key}); + ui->button_right_optional_num->setText(QChar{initialize_parameters.right_optional_symbol_key}); + current_text = initialize_parameters.initial_text; cursor_position = initialize_parameters.initial_cursor_position; @@ -932,6 +937,15 @@ void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { button->setEnabled(true); } } + + const auto enable_left_optional = initialize_parameters.left_optional_symbol_key != '\0'; + const auto enable_right_optional = initialize_parameters.right_optional_symbol_key != '\0'; + + ui->button_left_optional_num->setEnabled(enable_left_optional); + ui->button_left_optional_num->setVisible(enable_left_optional); + + ui->button_right_optional_num->setEnabled(enable_right_optional); + ui->button_right_optional_num->setVisible(enable_right_optional); break; } } @@ -1019,7 +1033,10 @@ bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { } if (bottom_osk_index == BottomOSKIndex::NumberPad && - std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + std::any_of(input_text.begin(), input_text.end(), [this](QChar c) { + return !c.isDigit() && c != QChar{initialize_parameters.left_optional_symbol_key} && + c != QChar{initialize_parameters.right_optional_symbol_key}; + })) { return false; } @@ -1384,6 +1401,10 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { } }; + // Store the initial row and column. + const auto initial_row = row; + const auto initial_column = column; + switch (bottom_osk_index) { case BottomOSKIndex::LowerCase: case BottomOSKIndex::UpperCase: { @@ -1394,6 +1415,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { auto* curr_button = keyboard_buttons[index][row][column]; while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + // If we returned back to where we started from, break the loop. + if (row == initial_row && column == initial_column) { + break; + } + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); curr_button = keyboard_buttons[index][row][column]; } @@ -1408,6 +1434,11 @@ void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { auto* curr_button = numberpad_buttons[row][column]; while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + // If we returned back to where we started from, break the loop. + if (row == initial_row && column == initial_column) { + break; + } + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); curr_button = numberpad_buttons[row][column]; } diff --git a/src/yuzu/applets/qt_software_keyboard.h b/src/yuzu/applets/qt_software_keyboard.h index 1c489fbb6..35d4ee2ef 100644 --- a/src/yuzu/applets/qt_software_keyboard.h +++ b/src/yuzu/applets/qt_software_keyboard.h @@ -211,7 +211,7 @@ private: std::array<std::array<QPushButton*, NUM_COLUMNS_NUMPAD>, NUM_ROWS_NUMPAD> numberpad_buttons; // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. - std::array<QPushButton*, 110> all_buttons; + std::array<QPushButton*, 112> all_buttons; std::size_t row{0}; std::size_t column{0}; diff --git a/src/yuzu/applets/qt_software_keyboard.ui b/src/yuzu/applets/qt_software_keyboard.ui index b0a1fcde9..9661cb260 100644 --- a/src/yuzu/applets/qt_software_keyboard.ui +++ b/src/yuzu/applets/qt_software_keyboard.ui @@ -3298,6 +3298,24 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="4" column="2"> + <widget class="QPushButton" name="button_left_optional_num"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>28</pointsize> + </font> + </property> + <property name="text"> + <string notr="true"></string> + </property> + </widget> + </item> <item row="4" column="3"> <widget class="QPushButton" name="button_0_num"> <property name="sizePolicy"> @@ -3316,6 +3334,24 @@ p, li { white-space: pre-wrap; } </property> </widget> </item> + <item row="4" column="4"> + <widget class="QPushButton" name="button_right_optional_num"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>28</pointsize> + </font> + </property> + <property name="text"> + <string notr="true"></string> + </property> + </widget> + </item> <item row="1" column="4"> <widget class="QPushButton" name="button_3_num"> <property name="sizePolicy"> @@ -3494,7 +3530,9 @@ p, li { white-space: pre-wrap; } <tabstop>button_7_num</tabstop> <tabstop>button_8_num</tabstop> <tabstop>button_9_num</tabstop> + <tabstop>button_left_optional_num</tabstop> <tabstop>button_0_num</tabstop> + <tabstop>button_right_optional_num</tabstop> </tabstops> <resources> <include location="../../../dist/icons/overlay/overlay.qrc"/> diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 0ee3820a2..d3fbdb09d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <glad/glad.h> @@ -805,6 +804,7 @@ void GRenderWindow::TouchEndEvent() { } void GRenderWindow::InitializeCamera() { + constexpr auto camera_update_ms = std::chrono::milliseconds{50}; // (50ms, 20Hz) if (!Settings::values.enable_ir_sensor) { return; } @@ -815,6 +815,12 @@ void GRenderWindow::InitializeCamera() { if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() || Settings::values.ir_sensor_device.GetValue() == "Auto") { camera = std::make_unique<QCamera>(cameraInfo); + if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && + !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + LOG_ERROR(Frontend, + "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + continue; + } camera_found = true; break; } @@ -825,10 +831,22 @@ void GRenderWindow::InitializeCamera() { } camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + + if (!camera_capture->isCaptureDestinationSupported( + QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { + LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); + return; + } + + camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, &GRenderWindow::OnCameraCapture); camera->unload(); - camera->setCaptureMode(QCamera::CaptureViewfinder); + if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { + camera->setCaptureMode(QCamera::CaptureViewfinder); + } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + camera->setCaptureMode(QCamera::CaptureStillImage); + } camera->load(); camera->start(); @@ -838,7 +856,7 @@ void GRenderWindow::InitializeCamera() { camera_timer = std::make_unique<QTimer>(); connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); }); // This timer should be dependent of camera resolution 5ms for every 100 pixels - camera_timer->start(100); + camera_timer->start(camera_update_ms); } void GRenderWindow::FinalizeCamera() { @@ -1089,8 +1107,8 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { } if (!unsupported_ext.empty()) { - LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", - glGetString(GL_RENDERER)); + const std::string gl_renderer{reinterpret_cast<const char*>(glGetString(GL_RENDERER))}; + LOG_ERROR(Frontend, "GPU does not support all required extensions: {}", gl_renderer); } for (const QString& ext : unsupported_ext) { LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString()); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index b4781e697..c45ebf1a2 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 2442bb3c3..f46fff340 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QButtonGroup> #include <QMessageBox> diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h index e2b2522bd..3252fc47a 100644 --- a/src/yuzu/compatdb.h +++ b/src/yuzu/compatdb.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 3b22102a8..a4ed68422 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> #include <QKeySequence> @@ -16,8 +15,7 @@ namespace FS = Common::FS; -Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) - : type(config_type), system{system_} { +Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { global = config_type == ConfigType::GlobalConfig; Initialize(config_name); @@ -74,7 +72,7 @@ const std::array<int, 2> Config::default_ringcon_analogs{{ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut}}, - {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("+"), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"), QStringLiteral("Home+L"), Qt::ApplicationShortcut}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}}, @@ -547,6 +545,7 @@ void Config::ReadDebuggingValues() { ReadBasicSetting(Settings::values.use_debug_asserts); ReadBasicSetting(Settings::values.use_auto_stub); ReadBasicSetting(Settings::values.enable_all_controllers); + ReadBasicSetting(Settings::values.create_crash_dumps); qt_config->endGroup(); } @@ -685,6 +684,7 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.shader_backend); ReadGlobalSetting(Settings::values.use_asynchronous_shaders); ReadGlobalSetting(Settings::values.use_fast_gpu_time); + ReadGlobalSetting(Settings::values.use_pessimistic_flushes); ReadGlobalSetting(Settings::values.bg_red); ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); @@ -1161,6 +1161,7 @@ void Config::SaveDebuggingValues() { WriteBasicSetting(Settings::values.use_debug_asserts); WriteBasicSetting(Settings::values.disable_macro_jit); WriteBasicSetting(Settings::values.enable_all_controllers); + WriteBasicSetting(Settings::values.create_crash_dumps); qt_config->endGroup(); } @@ -1301,6 +1302,7 @@ void Config::SaveRendererValues() { Settings::values.shader_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_shaders); WriteGlobalSetting(Settings::values.use_fast_gpu_time); + WriteGlobalSetting(Settings::values.use_pessimistic_flushes); WriteGlobalSetting(Settings::values.bg_red); WriteGlobalSetting(Settings::values.bg_green); WriteGlobalSetting(Settings::values.bg_blue); @@ -1546,7 +1548,6 @@ void Config::Reload() { ReadValues(); // To apply default value changes SaveValues(); - system.ApplySettings(); } void Config::Save() { diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 937b2d95b..06fa7d2d0 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -26,7 +25,7 @@ public: InputProfile, }; - explicit Config(Core::System& system_, const std::string& config_name = "qt-config", + explicit Config(const std::string& config_name = "qt-config", ConfigType config_type = ConfigType::GlobalConfig); ~Config(); @@ -195,8 +194,6 @@ private: std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; bool global; - - Core::System& system; }; // These metatype declarations cannot be in common/settings.h because core is devoid of QT diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp index dd4959417..97fb664bf 100644 --- a/src/yuzu/configuration/configuration_shared.cpp +++ b/src/yuzu/configuration/configuration_shared.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QCheckBox> #include <QObject> diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h index 56800b6ff..e597dcdb5 100644 --- a/src/yuzu/configuration/configuration_shared.h +++ b/src/yuzu/configuration/configuration_shared.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index a5bcee415..6034d8581 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -120,10 +120,10 @@ </sizepolicy> </property> <property name="maximum"> - <number>100</number> + <number>200</number> </property> <property name="pageStep"> - <number>10</number> + <number>5</number> </property> <property name="orientation"> <enum>Qt::Horizontal</enum> diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp index 73cdcf3f2..2a61de2a1 100644 --- a/src/yuzu/configuration/configure_camera.cpp +++ b/src/yuzu/configuration/configure_camera.cpp @@ -42,6 +42,12 @@ void ConfigureCamera::PreviewCamera() { LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(), cameraInfo.deviceName().toStdString()); camera = std::make_unique<QCamera>(cameraInfo); + if (!camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder) && + !camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + LOG_ERROR(Frontend, + "Camera doesn't support CaptureViewfinder or CaptureStillImage"); + continue; + } camera_found = true; break; } @@ -57,10 +63,22 @@ void ConfigureCamera::PreviewCamera() { } camera_capture = std::make_unique<QCameraImageCapture>(camera.get()); + + if (!camera_capture->isCaptureDestinationSupported( + QCameraImageCapture::CaptureDestination::CaptureToBuffer)) { + LOG_ERROR(Frontend, "Camera doesn't support saving to buffer"); + return; + } + + camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer); connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this, &ConfigureCamera::DisplayCapturedFrame); camera->unload(); - camera->setCaptureMode(QCamera::CaptureViewfinder); + if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureViewfinder)) { + camera->setCaptureMode(QCamera::CaptureViewfinder); + } else if (camera->isCaptureModeSupported(QCamera::CaptureMode::CaptureStillImage)) { + camera->setCaptureMode(QCamera::CaptureStillImage); + } camera->load(); camera->start(); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 84808f678..622808e94 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -1,8 +1,8 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QDesktopServices> +#include <QMessageBox> #include <QUrl> #include "common/fs/path_util.h" #include "common/logging/backend.h" @@ -15,7 +15,7 @@ #include "yuzu/uisettings.h" ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) - : QWidget(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} { + : QScrollArea(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} { ui->setupUi(this); SetConfiguration(); @@ -27,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) connect(ui->toggle_gdbstub, &QCheckBox::toggled, [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); + + connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { + if (crash_dump_warning_shown) { + return; + } + QMessageBox::warning(this, tr("Restart Required"), + tr("yuzu is required to restart in order to apply this setting."), + QMessageBox::Ok, QMessageBox::Ok); + crash_dump_warning_shown = true; + }); } ConfigureDebug::~ConfigureDebug() = default; @@ -72,7 +82,14 @@ void ConfigureDebug::SetConfiguration() { ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else ui->disable_web_applet->setEnabled(false); - ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); + ui->disable_web_applet->setText(tr("Web applet not compiled")); +#endif + +#ifdef YUZU_DBGHELP + ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); +#else + ui->create_crash_dumps->setEnabled(false); + ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); #endif } @@ -85,6 +102,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); + Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index 73f71c9e3..030a0b7f7 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -1,11 +1,10 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include <memory> -#include <QWidget> +#include <QScrollArea> namespace Core { class System; @@ -15,7 +14,7 @@ namespace Ui { class ConfigureDebug; } -class ConfigureDebug : public QWidget { +class ConfigureDebug : public QScrollArea { Q_OBJECT public: @@ -33,4 +32,6 @@ private: std::unique_ptr<Ui::ConfigureDebug> ui; const Core::System& system; + + bool crash_dump_warning_shown{false}; }; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 4c16274fc..314d47af5 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -1,62 +1,66 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ConfigureDebug</class> - <widget class="QWidget" name="ConfigureDebug"> + <widget class="QScrollArea" name="ConfigureDebug"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget"> <layout class="QVBoxLayout" name="verticalLayout_1"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Debugger</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Debugger</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_11"> + <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_11"> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_11"> - <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_11"> - <property name="text"> - <string>Port:</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="gdbport_spinbox"> - <property name="minimum"> - <number>1024</number> - </property> - <property name="maximum"> - <number>65535</number> - </property> - </widget> - </item> - </layout> + <widget class="QSpinBox" name="gdbport_spinbox"> + <property name="minimum"> + <number>1024</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> </item> </layout> - </widget> - </item> - </layout> + </item> + </layout> + </widget> </item> + </layout> + </item> <item> <widget class="QGroupBox" name="groupBox_2"> <property name="title"> @@ -227,6 +231,13 @@ <string>Debugging</string> </property> <layout class="QGridLayout" name="gridLayout_3"> + <item row="2" column="0"> + <widget class="QCheckBox" name="reporting_services"> + <property name="text"> + <string>Enable Verbose Reporting Services**</string> + </property> + </widget> + </item> <item row="0" column="0"> <widget class="QCheckBox" name="fs_access_log"> <property name="text"> @@ -234,20 +245,20 @@ </property> </widget> </item> - <item row="1" column="0"> + <item row="0" column="1"> <widget class="QCheckBox" name="dump_audio_commands"> - <property name="text"> - <string>Dump Audio Commands To Console**</string> - </property> <property name="toolTip"> <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> </property> + <property name="text"> + <string>Dump Audio Commands To Console**</string> + </property> </widget> </item> - <item row="2" column="0"> - <widget class="QCheckBox" name="reporting_services"> + <item row="2" column="1"> + <widget class="QCheckBox" name="create_crash_dumps"> <property name="text"> - <string>Enable Verbose Reporting Services**</string> + <string>Create Minidump After Crash</string> </property> </widget> </item> @@ -322,6 +333,7 @@ </item> </layout> </widget> + </widget> <tabstops> <tabstop>log_filter_edit</tabstop> <tabstop>toggle_console</tabstop> @@ -335,7 +347,6 @@ <tabstop>disable_loop_safety_checks</tabstop> <tabstop>fs_access_log</tabstop> <tabstop>reporting_services</tabstop> - <tabstop>dump_audio_commands</tabstop> <tabstop>quest_flag</tabstop> <tabstop>enable_cpu_debugging</tabstop> <tabstop>use_debug_asserts</tabstop> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 92ef4467b..4301313cf 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <memory> #include "common/logging/log.h" diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index cec1610ad..1f724834a 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 2a446205b..7ade01ba6 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <functional> #include <utility> diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index b6f3bb5ed..a090c1a3f 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 6b33c4535..87e5d0f48 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later // Include this early to include Vulkan headers how we want to #include "video_core/vulkan_common/vulkan_wrapper.h" diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 1b101c940..70034eb1b 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 7c3196c83..01f074699 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( @@ -55,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, + ui->use_pessimistic_flushes, use_pessimistic_flushes); } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -77,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->use_pessimistic_flushes->setEnabled( + Settings::values.use_pessimistic_flushes.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); @@ -89,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, + Settings::values.use_pessimistic_flushes, + use_pessimistic_flushes); ConfigurationShared::SetColoredComboBox( ui->gpu_accuracy, ui->label_gpu_accuracy, static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index 1ef7bd916..12e816905 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -39,6 +39,7 @@ private: ConfigurationShared::CheckState use_vsync; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; + ConfigurationShared::CheckState use_pessimistic_flushes; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index 96de0b3d1..87a121471 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -75,7 +75,7 @@ <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> </property> <property name="text"> - <string>Use VSync (OpenGL only)</string> + <string>Use VSync</string> </property> </widget> </item> @@ -100,6 +100,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_pessimistic_flushes"> + <property name="toolTip"> + <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> + </property> + <property name="text"> + <string>Use pessimistic buffer flushes (Hack)</string> + </property> + </widget> + </item> + <item> <widget class="QWidget" name="af_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_1"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index edf0893c4..daa77a8f8 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QMenu> #include <QMessageBox> diff --git a/src/yuzu/configuration/configure_hotkeys.h b/src/yuzu/configuration/configure_hotkeys.h index f943ec538..b45ecb185 100644 --- a/src/yuzu/configuration/configure_hotkeys.h +++ b/src/yuzu/configuration/configure_hotkeys.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index f1b061b13..cb55472c9 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <memory> #include <thread> @@ -66,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system) ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - profiles(std::make_unique<InputProfiles>(system_)), system{system_} { + profiles(std::make_unique<InputProfiles>()), system{system_} { ui->setupUi(this); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 4cafa3dab..c89189c36 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index f3be9a374..9e5a40fe7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <memory> @@ -162,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); const auto common_button_name = input_subsystem->GetButtonName(param); // Retrieve the names from Qt @@ -185,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); - return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); + return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); } if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); @@ -363,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(tr("[not set]")); }); if (param.Has("code") || param.Has("button") || param.Has("hat")) { - context_menu.addAction(tr("Toggle button"), [&] { - const bool toggle_value = !param.Get("toggle", false); - param.Set("toggle", toggle_value); - button_map[button_id]->setText(ButtonToText(param)); - emulated_controller->SetButtonParam(button_id, param); - }); context_menu.addAction(tr("Invert button"), [&] { const bool invert_value = !param.Get("inverted", false); param.Set("inverted", invert_value); button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle button"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } if (param.Has("axis")) { context_menu.addAction(tr("Invert axis"), [&] { @@ -399,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle axis"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); }); @@ -1411,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick( ui->controllerFrame->BeginMappingAnalog(button_id); } - timeout_timer->start(2500); // Cancel after 2.5 seconds + timeout_timer->start(4000); // Cancel after 4 seconds poll_timer->start(25); // Check for new inputs every 25ms } @@ -1476,7 +1482,7 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { void ConfigureInputPlayer::CreateProfile() { const auto profile_name = - LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20, + LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30, LimitableInputDialog::InputLimiter::Filesystem); if (profile_name.isEmpty()) { diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index 47df6b3d3..79434fdd8 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index c313b0919..d1b870c72 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <sstream> diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h index 91d1ae671..7dcc9318e 100644 --- a/src/yuzu/configuration/configure_motion_touch.h +++ b/src/yuzu/configuration/configure_motion_touch.h @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index af8343b2e..c3cb8f61d 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); - game_config = - std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig); + game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig); addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); audio_tab = std::make_unique<ConfigureAudio>(system_, this); diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 4906997ab..674a75a62 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <memory> diff --git a/src/yuzu/configuration/configure_per_game_addons.h b/src/yuzu/configuration/configure_per_game_addons.h index 14690fba8..53db405c1 100644 --- a/src/yuzu/configuration/configure_per_game_addons.h +++ b/src/yuzu/configuration/configure_per_game_addons.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index 5442fe328..5c0217ba8 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> #include <QFileDialog> diff --git a/src/yuzu/configuration/configure_profile_manager.h b/src/yuzu/configuration/configure_profile_manager.h index 575cb89d5..fe9033779 100644 --- a/src/yuzu/configuration/configure_profile_manager.h +++ b/src/yuzu/configuration/configure_profile_manager.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index ecebb0fb7..bc9d9d77a 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <chrono> #include <optional> diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index 5a1633192..8f02880a7 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index cf88a5bf0..625af0c89 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -16,6 +16,9 @@ <property name="text"> <string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></string> </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> </widget> </item> <item row="1" column="0" colspan="4"> diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp index 06cc452c3..18e2eba69 100644 --- a/src/yuzu/configuration/configure_touch_from_button.cpp +++ b/src/yuzu/configuration/configure_touch_from_button.cpp @@ -1,6 +1,5 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2020 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QInputDialog> #include <QKeyEvent> diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h index b8c55db66..5a1416d00 100644 --- a/src/yuzu/configuration/configure_touch_from_button.h +++ b/src/yuzu/configuration/configure_touch_from_button.h @@ -1,6 +1,5 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2020 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h index 347b46583..49f533afe 100644 --- a/src/yuzu/configuration/configure_touch_widget.h +++ b/src/yuzu/configuration/configure_touch_widget.h @@ -1,6 +1,5 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2020 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp index 29c86c7bc..5a03e48df 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <memory> #include "ui_configure_touchscreen_advanced.h" diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h index 72061492c..034dc0d46 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.h +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index d3a60cdd1..48f71b53c 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> #include <utility> @@ -220,6 +219,7 @@ void ConfigureUi::InitializeLanguageComboBox() { for (const auto& lang : languages) { if (QString::fromLatin1(lang.id) == QStringLiteral("en")) { ui->language_combobox->addItem(lang.name, QStringLiteral("en")); + language_files.removeOne(QStringLiteral("en.qm")); continue; } for (int i = 0; i < language_files.size(); ++i) { diff --git a/src/yuzu/configuration/configure_ui.h b/src/yuzu/configuration/configure_ui.h index 48b6e6d82..95af8370e 100644 --- a/src/yuzu/configuration/configure_ui.h +++ b/src/yuzu/configuration/configure_ui.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index ff4bf44f4..d668c992b 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QIcon> #include <QMessageBox> diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index 041b51149..03feb55f8 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -1,6 +1,5 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2017 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 20b22e7de..807afbeb2 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { } // namespace -InputProfiles::InputProfiles(Core::System& system_) : system{system_} { +InputProfiles::InputProfiles() { const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; if (!FS::IsDir(input_profile_loc)) { @@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} { if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { map_profiles.insert_or_assign( - name_without_ext, std::make_unique<Config>(system, name_without_ext, - Config::ConfigType::InputProfile)); + name_without_ext, + std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile)); } return true; @@ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p } map_profiles.insert_or_assign( - profile_name, - std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile)); + profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); return SaveProfile(profile_name, player_index); } diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 65fc9e62c..2bf3e4250 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h @@ -15,7 +15,7 @@ class Config; class InputProfiles { public: - explicit InputProfiles(Core::System& system_); + explicit InputProfiles(); virtual ~InputProfiles(); std::vector<std::string> GetInputProfileNames(); @@ -31,6 +31,4 @@ private: bool ProfileExistsInMap(const std::string& profile_name) const; std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; - - Core::System& system; }; diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 6b834c42e..e4bf16a04 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QAction> #include <QLayout> diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index 52cea3326..9651dfaa9 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 33110685a..d3e2d3c12 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QAction> #include <QLayout> diff --git a/src/yuzu/debugger/profiler.h b/src/yuzu/debugger/profiler.h index 8e69fdb06..4c8ccd3c2 100644 --- a/src/yuzu/debugger/profiler.h +++ b/src/yuzu/debugger/profiler.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 0ea31cd33..7f7c5fc42 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> #include <fmt/format.h> diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index f21b9f467..7e528b592 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h index a867cc4d6..e08784498 100644 --- a/src/yuzu/discord.h +++ b/src/yuzu/discord.h @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp index 66f928af6..c351e9b83 100644 --- a/src/yuzu/discord_impl.cpp +++ b/src/yuzu/discord_impl.cpp @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <chrono> #include <string> diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h index 03ad42681..84710b9c6 100644 --- a/src/yuzu/discord_impl.h +++ b/src/yuzu/discord_impl.h @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 5bcf582bf..b127badc2 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <regex> #include <QApplication> @@ -127,10 +126,8 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { layout_filter = new QHBoxLayout; layout_filter->setContentsMargins(8, 8, 8, 8); label_filter = new QLabel; - label_filter->setText(tr("Filter:")); edit_filter = new QLineEdit; edit_filter->clear(); - edit_filter->setPlaceholderText(tr("Enter pattern to filter")); edit_filter->installEventFilter(key_release_eater); edit_filter->setClearButtonEnabled(true); connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); @@ -150,6 +147,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { layout_filter->addWidget(label_filter_result); layout_filter->addWidget(button_filter_close); setLayout(layout_filter); + RetranslateUI(); } /** @@ -287,7 +285,7 @@ void GameList::OnUpdateThemedIcons() { } case GameListItemType::AddDir: child->setData( - QIcon::fromTheme(QStringLiteral("plus")) + QIcon::fromTheme(QStringLiteral("list-add")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::DecorationRole); @@ -334,13 +332,9 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); item_model->insertColumns(0, COLUMN_COUNT); - item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); - item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); + RetranslateUI(); - item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); - item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); - item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); item_model->setSortRole(GameListItemPath::SortRole); connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); @@ -754,6 +748,35 @@ void GameList::LoadCompatibilityList() { } } +void GameList::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameList::RetranslateUI() { + item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); + item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); + item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); + item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); + item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); +} + +void GameListSearchField::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameListSearchField::RetranslateUI() { + label_filter->setText(tr("Filter:")); + edit_filter->setPlaceholderText(tr("Enter pattern to filter")); +} + QStandardItemModel* GameList::GetModel() const { return item_model; } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 9605985cc..cdf085019 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -141,6 +140,9 @@ private: void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); void AddFavoritesPopup(QMenu& context_menu); + void changeEvent(QEvent*) override; + void RetranslateUI(); + std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; GameListSearchField* search_field; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index cd7d63536..6198d1e4e 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -295,7 +294,7 @@ public: const int icon_size = UISettings::values.folder_icon_size.GetValue(); - setData(QIcon::fromTheme(QStringLiteral("plus")) + setData(QIcon::fromTheme(QStringLiteral("list-add")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::DecorationRole); @@ -354,6 +353,9 @@ public: void setFocus(); private: + void changeEvent(QEvent*) override; + void RetranslateUI(); + class KeyReleaseEater : public QObject { public: explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index d59aa5d18..13723f6e5 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <sstream> #include <QShortcut> diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index 57a7c7da5..dc5b7f628 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index e273744fd..e263a07a7 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -147,6 +147,10 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size ui->progress_bar->setMaximum(static_cast<int>(total)); previous_total = total; } + // Reset the progress bar ranges if compilation is done + if (stage == VideoCore::LoadCallbackStage::Complete) { + ui->progress_bar->setRange(0, 0); + } QString estimate; // If theres a drastic slowdown in the rate, then display an estimate diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e56fcabff..bda9986e1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <cinttypes> #include <clocale> @@ -9,6 +8,10 @@ #ifdef __APPLE__ #include <unistd.h> // for chdir #endif +#ifdef __linux__ +#include <csignal> +#include <sys/socket.h> +#endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "applets/qt_controller.h" @@ -135,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" +#ifdef YUZU_DBGHELP +#include "yuzu/mini_dump.h" +#endif + using namespace Common::Literals; #ifdef USE_DISCORD_PRESENCE @@ -254,12 +261,27 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } -GMainWindow::GMainWindow(bool has_broken_vulkan) +bool GMainWindow::CheckDarkMode() { +#ifdef __linux__ + const QPalette test_palette(qApp->palette()); + const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text); + const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); + return (text_color.value() > window_color.value()); +#else + // TODO: Windows + return false; +#endif // __linux__ +} + +GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan) : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, - input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, - config{std::make_unique<Config>(*system)}, + input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, provider{std::make_unique<FileSys::ManualContentProvider>()} { +#ifdef __linux__ + SetupSigInterrupts(); +#endif + Common::Log::Initialize(); LoadTranslation(); @@ -267,6 +289,13 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) ui->setupUi(this); statusBar()->hide(); + // Check dark mode before a theme is loaded + os_dark_mode = CheckDarkMode(); + startup_icon_theme = QIcon::themeName(); + // fallback can only be set once, colorful theme icons are okay on both light/dark + QIcon::setFallbackThemeName(QStringLiteral("colorful")); + QIcon::setFallbackSearchPaths(QStringList(QStringLiteral(":/icons"))); + default_theme_paths = QIcon::themeSearchPaths(); UpdateUITheme(); @@ -379,6 +408,8 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) SDL_EnableScreenSaver(); #endif + SetupPrepareForSleep(); + Common::Log::Start(); QStringList args = QApplication::arguments(); @@ -463,7 +494,11 @@ GMainWindow::~GMainWindow() { if (render_window->parent() == nullptr) { delete render_window; } - system->GetRoomNetwork().Shutdown(); + +#ifdef __linux__ + ::close(sig_interrupt_fds[0]); + ::close(sig_interrupt_fds[1]); +#endif } void GMainWindow::RegisterMetaTypes() { @@ -828,7 +863,7 @@ void GMainWindow::InitializeWidgets() { }); multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room, - ui->action_Show_Room, system->GetRoomNetwork()); + ui->action_Show_Room, *system); multiplayer_state->setVisible(false); // Create status bar @@ -1060,14 +1095,26 @@ void GMainWindow::InitializeHotkeys() { connect_shortcut(QStringLiteral("Audio Mute/Unmute"), [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); connect_shortcut(QStringLiteral("Audio Volume Down"), [] { - const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); - const auto new_volume = std::max(current_volume - 5, 0); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); + int step = 5; + if (current_volume <= 30) { + step = 2; + } + if (current_volume <= 6) { + step = 1; + } + Settings::values.volume.SetValue(std::max(current_volume - step, 0)); }); connect_shortcut(QStringLiteral("Audio Volume Up"), [] { - const auto current_volume = static_cast<int>(Settings::values.volume.GetValue()); - const auto new_volume = std::min(current_volume + 5, 100); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue()); + int step = 5; + if (current_volume < 30) { + step = 2; + } + if (current_volume < 6) { + step = 1; + } + Settings::values.volume.SetValue(current_volume + step); }); connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); @@ -1314,6 +1361,43 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } +void GMainWindow::SetupPrepareForSleep() { +#ifdef __linux__ + auto bus = QDBusConnection::systemBus(); + if (bus.isConnected()) { + const bool success = bus.connect( + QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), + QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"), + QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool))); + + if (!success) { + LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal"); + } + } else { + LOG_WARNING(Frontend, "QDBusConnection system bus is not connected"); + } +#endif // __linux__ +} + +void GMainWindow::OnPrepareForSleep(bool prepare_sleep) { + if (emu_thread == nullptr) { + return; + } + + if (prepare_sleep) { + if (emu_thread->IsRunning()) { + auto_paused = true; + OnPauseGame(); + } + } else { + if (!emu_thread->IsRunning() && auto_paused) { + auto_paused = false; + RequestGameResume(); + OnStartGame(); + } + } +} + #ifdef __linux__ static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) { if (!QDBusConnection::sessionBus().isConnected()) { @@ -1353,6 +1437,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) { QString::fromLatin1("org.freedesktop.portal.Request")); unlocker.call(QString::fromLatin1("Close")); } + +std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0}; + +void GMainWindow::SetupSigInterrupts() { + if (sig_interrupt_fds[2] == 1) { + return; + } + socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data()); + sig_interrupt_fds[2] = 1; + + struct sigaction sa; + sa.sa_handler = &GMainWindow::HandleSigInterrupt; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sa, nullptr); + sigaction(SIGTERM, &sa, nullptr); + + sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this); + connect(sig_interrupt_notifier, &QSocketNotifier::activated, this, + &GMainWindow::OnSigInterruptNotifierActivated); + connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close); +} + +void GMainWindow::HandleSigInterrupt(int sig) { + if (sig == SIGINT) { + exit(1); + } + + // Calling into Qt directly from a signal handler is not safe, + // so wake up a QSocketNotifier with this hacky write call instead. + char a = 1; + int ret = write(sig_interrupt_fds[0], &a, sizeof(a)); + (void)ret; +} + +void GMainWindow::OnSigInterruptNotifierActivated() { + sig_interrupt_notifier->setEnabled(false); + + char a; + int ret = read(sig_interrupt_fds[1], &a, sizeof(a)); + (void)ret; + + sig_interrupt_notifier->setEnabled(true); + + emit SigInterrupt(); +} #endif // __linux__ void GMainWindow::PreventOSSleep() { @@ -1476,17 +1606,18 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p return true; } -void GMainWindow::SelectAndSetCurrentUser() { +bool GMainWindow::SelectAndSetCurrentUser() { QtProfileSelectionDialog dialog(system->HIDCore(), this); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec() == QDialog::Rejected) { - return; + return false; } Settings::values.current_user = dialog.GetIndex(); + return true; } void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, @@ -1509,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); - Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); + Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); + system->ApplySettings(); } // Save configurations @@ -1520,11 +1652,14 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { - SelectAndSetCurrentUser(); + if (SelectAndSetCurrentUser() == false) { + return; + } } - if (!LoadROM(filename, program_id, program_index)) + if (!LoadROM(filename, program_id, program_index)) { return; + } system->SetShuttingDown(false); @@ -2850,7 +2985,7 @@ void GMainWindow::OnConfigure() { Settings::values.disabled_addons.clear(); - config = std::make_unique<Config>(*system); + config = std::make_unique<Config>(); UISettings::values.reset_to_defaults = false; UISettings::values.game_dirs = std::move(old_game_dirs); @@ -2911,6 +3046,7 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); controller_dialog->refreshConfiguration(); + system->ApplySettings(); } void GMainWindow::OnConfigureTas() { @@ -3222,7 +3358,8 @@ void GMainWindow::MigrateConfigFiles() { } const auto origin = config_dir_fs_path / filename; const auto destination = config_dir_fs_path / "custom" / filename; - LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination); + LOG_INFO(Frontend, "Migrating config file from {} to {}", origin.string(), + destination.string()); if (!Common::FS::RenameFile(origin, destination)) { // Delete the old config file if one already exists in the new location. Common::FS::RemoveFile(origin); @@ -3697,6 +3834,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); + system->GetRoomNetwork().Shutdown(); QWidget::closeEvent(event); } @@ -3818,8 +3956,21 @@ void GMainWindow::filterBarSetChecked(bool state) { emit(OnToggleFilterBar()); } +static void AdjustLinkColor() { + QPalette new_pal(qApp->palette()); + if (UISettings::IsDarkTheme()) { + new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); + } else { + new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); + } + if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) { + qApp->setPalette(new_pal); + } +} + void GMainWindow::UpdateUITheme() { - const QString default_theme = QStringLiteral("default"); + const QString default_theme = + QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second); QString current_theme = UISettings::values.theme; QStringList theme_paths(default_theme_paths); @@ -3827,6 +3978,23 @@ void GMainWindow::UpdateUITheme() { current_theme = default_theme; } +#ifdef _WIN32 + QIcon::setThemeName(current_theme); + AdjustLinkColor(); +#else + if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) { + QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme + : startup_icon_theme); + QIcon::setThemeSearchPaths(theme_paths); + if (CheckDarkMode()) { + current_theme = QStringLiteral("default_dark"); + } + } else { + QIcon::setThemeName(current_theme); + QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons"))); + AdjustLinkColor(); + } +#endif if (current_theme != default_theme) { QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)}; QFile f(theme_uri); @@ -3849,25 +4017,9 @@ void GMainWindow::UpdateUITheme() { qApp->setStyleSheet({}); setStyleSheet({}); } - - QPalette new_pal(qApp->palette()); - if (UISettings::IsDarkTheme()) { - new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); - } else { - new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); - } - qApp->setPalette(new_pal); - - QIcon::setThemeName(current_theme); - QIcon::setThemeSearchPaths(theme_paths); } void GMainWindow::LoadTranslation() { - // If the selected language is English, no need to install any translation - if (UISettings::values.language == QStringLiteral("en")) { - return; - } - bool loaded; if (UISettings::values.language.isEmpty()) { @@ -3910,12 +4062,49 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } +void GMainWindow::changeEvent(QEvent* event) { +#ifdef __linux__ + // PaletteChange event appears to only reach so far into the GUI, explicitly asking to + // UpdateUITheme is a decent work around + if (event->type() == QEvent::PaletteChange) { + const QPalette test_palette(qApp->palette()); + const QString current_theme = UISettings::values.theme; + // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too + static QColor last_window_color; + const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window); + if (last_window_color != window_color && (current_theme == QStringLiteral("default") || + current_theme == QStringLiteral("colorful"))) { + UpdateUITheme(); + } + last_window_color = window_color; + } +#endif // __linux__ + QWidget::changeEvent(event); +} + #ifdef main #undef main #endif int main(int argc, char* argv[]) { + std::unique_ptr<Config> config = std::make_unique<Config>(); bool has_broken_vulkan = false; + bool is_child = false; + if (CheckEnvVars(&is_child)) { + return 0; + } + +#ifdef YUZU_DBGHELP + PROCESS_INFORMATION pi; + if (!is_child && Settings::values.create_crash_dumps.GetValue() && + MiniDump::SpawnDebuggee(argv[0], pi)) { + // Delete the config object so that it doesn't save when the program exits + config.reset(nullptr); + MiniDump::DebugDebuggee(pi); + return 0; + } +#endif + if (StartupChecks(argv[0], &has_broken_vulkan)) { return 0; } @@ -3955,11 +4144,20 @@ int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); + // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021 + // so we can see if we get \u3008 instead + // TL;DR all other number formats are consecutive in unicode code points + // This bug is fixed in Qt6, specifically 6.0.0-alpha1 + const QLocale locale = QLocale::system(); + if (QStringLiteral("\u3008") == locale.toString(1)) { + QLocale::setDefault(QLocale::system().name()); + } + // Qt changes the locale and causes issues in float conversion using std::to_string() when // generating shaders setlocale(LC_ALL, "C"); - GMainWindow main_window{has_broken_vulkan}; + GMainWindow main_window{std::move(config), has_broken_vulkan}; // After settings have been loaded by GMainWindow, apply the filter main_window.show(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8d5c1398f..716aef063 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -1,6 +1,5 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -121,7 +120,7 @@ class GMainWindow : public QMainWindow { public: void filterBarSetChecked(bool state); void UpdateUITheme(); - explicit GMainWindow(bool has_broken_vulkan); + explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan); ~GMainWindow() override; bool DropAction(QDropEvent* event); @@ -164,6 +163,8 @@ signals: void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); + void SigInterrupt(); + public slots: void OnLoadComplete(); void OnExecuteProgram(std::size_t program_index); @@ -203,7 +204,7 @@ private: void ConnectMenuEvents(); void UpdateMenuState(); - MultiplayerState* multiplayer_state = nullptr; + void SetupPrepareForSleep(); void PreventOSSleep(); void AllowOSSleep(); @@ -217,7 +218,7 @@ private: void SetDiscordEnabled(bool state); void LoadAmiibo(const QString& filename); - void SelectAndSetCurrentUser(); + bool SelectAndSetCurrentUser(); /** * Stores the filename in the recently loaded files list. @@ -250,14 +251,22 @@ private: bool ConfirmForceLockedExit(); void RequestGameExit(); void RequestGameResume(); + void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; +#ifdef __linux__ + void SetupSigInterrupts(); + static void HandleSigInterrupt(int); + void OnSigInterruptNotifierActivated(); +#endif + private slots: void OnStartGame(); void OnRestartGame(); void OnPauseGame(); void OnPauseContinueGame(); void OnStopGame(); + void OnPrepareForSleep(bool prepare_sleep); void OnMenuReportCompatibility(); void OnOpenModsPage(); void OnOpenQuickstartGuide(); @@ -339,6 +348,7 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); + bool CheckDarkMode(); QString GetTasStateDescription() const; @@ -348,6 +358,8 @@ private: std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; + MultiplayerState* multiplayer_state = nullptr; + GRenderWindow* render_window; GameList* game_list; LoadingScreen* loading_screen; @@ -382,6 +394,9 @@ private: QTimer mouse_hide_timer; QTimer mouse_center_timer; + QString startup_icon_theme; + bool os_dark_mode = false; + // FS std::shared_ptr<FileSys::VfsFilesystem> vfs; std::unique_ptr<FileSys::ManualContentProvider> provider; @@ -420,6 +435,9 @@ private: bool is_tas_recording_dialog_active{}; #ifdef __linux__ + QSocketNotifier* sig_interrupt_notifier; + static std::array<int, 3> sig_interrupt_fds; + QDBusObjectPath wake_lock{}; #endif diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp new file mode 100644 index 000000000..a34dc6a9c --- /dev/null +++ b/src/yuzu/mini_dump.cpp @@ -0,0 +1,202 @@ +// SPDX-FileCopyrightText: 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <cstdio> +#include <cstring> +#include <ctime> +#include <filesystem> +#include <fmt/format.h> +#include <windows.h> +#include "yuzu/mini_dump.h" +#include "yuzu/startup_checks.h" + +// dbghelp.h must be included after windows.h +#include <dbghelp.h> + +namespace MiniDump { + +void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, + EXCEPTION_POINTERS* pep) { + char file_name[255]; + const std::time_t the_time = std::time(nullptr); + std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); + + // Open the file + HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { + fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); + return; + } + + // Create the minidump + const MINIDUMP_TYPE dump_type = MiniDumpNormal; + + const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, + dump_type, (pep != 0) ? info : 0, 0, 0); + + if (write_dump_status) { + fmt::print(stderr, "MiniDump created: {}", file_name); + } else { + fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); + } + + // Close the file + CloseHandle(file_handle); +} + +void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { + EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + + HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); + if (thread_handle == nullptr) { + fmt::print(stderr, "OpenThread failed ({})", GetLastError()); + return; + } + + // Get child process context + CONTEXT context = {}; + context.ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(thread_handle, &context)) { + fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); + return; + } + + // Create exception pointers for minidump + EXCEPTION_POINTERS ep; + ep.ExceptionRecord = &record; + ep.ContextRecord = &context; + + MINIDUMP_EXCEPTION_INFORMATION info; + info.ThreadId = deb_ev.dwThreadId; + info.ExceptionPointers = &ep; + info.ClientPointers = false; + + CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); + + if (CloseHandle(thread_handle) == 0) { + fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); + } +} + +bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { + std::memset(&pi, 0, sizeof(pi)); + + // Don't debug if we are already being debugged + if (IsDebuggerPresent()) { + return false; + } + + if (!SpawnChild(arg0, &pi, 0)) { + fmt::print(stderr, "warning: continuing without crash dumps"); + return false; + } + + const bool can_debug = DebugActiveProcess(pi.dwProcessId); + if (!can_debug) { + fmt::print(stderr, + "warning: DebugActiveProcess failed ({}), continuing without crash dumps", + GetLastError()); + return false; + } + + return true; +} + +static const char* ExceptionName(DWORD exception) { + switch (exception) { + case EXCEPTION_ACCESS_VIOLATION: + return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_BREAKPOINT: + return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_SINGLE_STEP: + return "EXCEPTION_SINGLE_STEP"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "EXCEPTION_FLT_DENORMAL_OPERAND"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_INEXACT_RESULT: + return "EXCEPTION_FLT_INEXACT_RESULT"; + case EXCEPTION_FLT_INVALID_OPERATION: + return "EXCEPTION_FLT_INVALID_OPERATION"; + case EXCEPTION_FLT_OVERFLOW: + return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_STACK_CHECK: + return "EXCEPTION_FLT_STACK_CHECK"; + case EXCEPTION_FLT_UNDERFLOW: + return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: + return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_PRIV_INSTRUCTION: + return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_IN_PAGE_ERROR: + return "EXCEPTION_IN_PAGE_ERROR"; + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + case EXCEPTION_STACK_OVERFLOW: + return "EXCEPTION_STACK_OVERFLOW"; + case EXCEPTION_INVALID_DISPOSITION: + return "EXCEPTION_INVALID_DISPOSITION"; + case EXCEPTION_GUARD_PAGE: + return "EXCEPTION_GUARD_PAGE"; + case EXCEPTION_INVALID_HANDLE: + return "EXCEPTION_INVALID_HANDLE"; + default: + return "unknown exception type"; + } +} + +void DebugDebuggee(PROCESS_INFORMATION& pi) { + DEBUG_EVENT deb_ev = {}; + + while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { + const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); + if (!wait_success) { + fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError()); + return; + } + + switch (deb_ev.dwDebugEventCode) { + case OUTPUT_DEBUG_STRING_EVENT: + case CREATE_PROCESS_DEBUG_EVENT: + case CREATE_THREAD_DEBUG_EVENT: + case EXIT_PROCESS_DEBUG_EVENT: + case EXIT_THREAD_DEBUG_EVENT: + case LOAD_DLL_DEBUG_EVENT: + case RIP_EVENT: + case UNLOAD_DLL_DEBUG_EVENT: + // Continue on all other debug events + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); + break; + case EXCEPTION_DEBUG_EVENT: + EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; + + // We want to generate a crash dump if we are seeing the same exception again. + if (!deb_ev.u.Exception.dwFirstChance) { + fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n", + record.ExceptionCode, ExceptionName(record.ExceptionCode)); + DumpFromDebugEvent(deb_ev, pi); + } + + // Continue without handling the exception. + // Lets the debuggee use its own exception handler. + // - If one does not exist, we will see the exception once more where we make a minidump + // for. Then when it reaches here again, yuzu will probably crash. + // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an + // infinite loop of exceptions. + ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); + break; + } + } +} + +} // namespace MiniDump diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h new file mode 100644 index 000000000..d6b6cca84 --- /dev/null +++ b/src/yuzu/mini_dump.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <windows.h> + +#include <dbghelp.h> + +namespace MiniDump { + +void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, + EXCEPTION_POINTERS* pep); + +void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); +bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); +void DebugDebuggee(PROCESS_INFORMATION& pi); + +} // namespace MiniDump diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 5837b36ab..9e672f82e 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -16,7 +16,7 @@ #include <QUrl> #include <QtConcurrent/QtConcurrentRun> #include "common/logging/log.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "ui_chat_room.h" #include "yuzu/game_list_p.h" #include "yuzu/multiplayer/chat_room.h" @@ -122,19 +122,22 @@ public: static const int UsernameRole = Qt::UserRole + 2; static const int AvatarUrlRole = Qt::UserRole + 3; static const int GameNameRole = Qt::UserRole + 4; + static const int GameVersionRole = Qt::UserRole + 5; PlayerListItem() = default; explicit PlayerListItem(const std::string& nickname, const std::string& username, - const std::string& avatar_url, const std::string& game_name) { + const std::string& avatar_url, + const AnnounceMultiplayerRoom::GameInfo& game_info) { setEditable(false); setData(QString::fromStdString(nickname), NicknameRole); setData(QString::fromStdString(username), UsernameRole); setData(QString::fromStdString(avatar_url), AvatarUrlRole); - if (game_name.empty()) { + if (game_info.name.empty()) { setData(QObject::tr("Not playing a game"), GameNameRole); } else { - setData(QString::fromStdString(game_name), GameNameRole); + setData(QString::fromStdString(game_info.name), GameNameRole); } + setData(QString::fromStdString(game_info.version), GameVersionRole); } QVariant data(int role) const override { @@ -149,7 +152,13 @@ public: } else { name = QStringLiteral("%1 (%2)").arg(nickname, username); } - return QStringLiteral("%1\n %2").arg(name, data(GameNameRole).toString()); + const QString version = data(GameVersionRole).toString(); + QString version_string; + if (!version.isEmpty()) { + version_string = QStringLiteral("(%1)").arg(version); + } + return QStringLiteral("%1\n %2 %3") + .arg(name, data(GameNameRole).toString(), version_string); } }; @@ -167,6 +176,10 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C ui->chat_history->document()->setMaximumBlockCount(max_chat_lines); + auto font = ui->chat_history->font(); + font.setPointSizeF(10); + ui->chat_history->setFont(font); + // register the network structs to use in slots and signals qRegisterMetaType<Network::ChatEntry>(); qRegisterMetaType<Network::StatusMessageEntry>(); @@ -316,21 +329,19 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_ } void ChatRoom::OnSendChat() { - if (auto room = room_network->GetRoomMember().lock()) { - if (room->GetState() != Network::RoomMember::State::Joined && - room->GetState() != Network::RoomMember::State::Moderator) { - + if (auto room_member = room_network->GetRoomMember().lock()) { + if (!room_member->IsConnected()) { return; } auto message = ui->chat_message->text().toStdString(); if (!ValidateMessage(message)) { return; } - auto nick = room->GetNickname(); - auto username = room->GetUsername(); + auto nick = room_member->GetNickname(); + auto username = room_member->GetUsername(); Network::ChatEntry chat{nick, username, message}; - auto members = room->GetMemberInformation(); + auto members = room_member->GetMemberInformation(); auto it = std::find_if(members.begin(), members.end(), [&chat](const Network::RoomMember::MemberInformation& member) { return member.nickname == chat.nickname && @@ -341,7 +352,7 @@ void ChatRoom::OnSendChat() { } auto player = std::distance(members.begin(), it); ChatMessage m(chat, *room_network); - room->SendChatMessage(message); + room_member->SendChatMessage(message); AppendChatMessage(m.GetPlayerChatMessage(player)); ui->chat_message->clear(); } @@ -368,7 +379,7 @@ void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) if (member.nickname.empty()) continue; QStandardItem* name_item = new PlayerListItem(member.nickname, member.username, - member.avatar_url, member.game_info.name); + member.avatar_url, member.game_info); #ifdef ENABLE_WEB_SERVICE if (!icon_cache.count(member.avatar_url) && !member.avatar_url.empty()) { diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index a9859ed70..b34a8d004 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -10,7 +10,7 @@ #include <QTime> #include <QtConcurrent/QtConcurrentRun> #include "common/logging/log.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "ui_client_room.h" #include "yuzu/game_list_p.h" #include "yuzu/multiplayer/client_room.h" @@ -74,7 +74,6 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) { void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) { if (state == Network::RoomMember::State::Joined || state == Network::RoomMember::State::Moderator) { - ui->chat->Clear(); ui->chat->AppendStatusMessage(tr("Connected")); SetModPerms(state == Network::RoomMember::State::Moderator); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 9000c4531..017063074 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -8,6 +8,8 @@ #include <QString> #include <QtConcurrent/QtConcurrentRun> #include "common/settings.h" +#include "core/core.h" +#include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_direct_connect.h" #include "yuzu/main.h" @@ -20,9 +22,10 @@ enum class ConnectionType : u8 { TraversalServer, IP }; -DirectConnectWindow::DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent) +DirectConnectWindow::DirectConnectWindow(Core::System& system_, QWidget* parent) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui(std::make_unique<Ui::DirectConnect>()), room_network{room_network_} { + ui(std::make_unique<Ui::DirectConnect>()), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); @@ -53,10 +56,20 @@ void DirectConnectWindow::RetranslateUi() { } void DirectConnectWindow::Connect() { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } if (!ui->nickname->hasAcceptableInput()) { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); return; } + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } if (const auto member = room_network.GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { @@ -97,9 +110,9 @@ void DirectConnectWindow::Connect() { QFuture<void> f = QtConcurrent::run([&] { if (auto room_member = room_network.GetRoomMember().lock()) { auto port = UISettings::values.multiplayer_port.GetValue(); - room_member->Join(ui->nickname->text().toStdString(), "", - ui->ip->text().toStdString().c_str(), port, 0, - Network::NoPreferredMac, ui->password->text().toStdString().c_str()); + room_member->Join(ui->nickname->text().toStdString(), + ui->ip->text().toStdString().c_str(), port, 0, Network::NoPreferredIP, + ui->password->text().toStdString().c_str()); } }); watcher->setFuture(f); @@ -121,9 +134,7 @@ void DirectConnectWindow::OnConnection() { EndConnecting(); if (auto room_member = room_network.GetRoomMember().lock()) { - if (room_member->GetState() == Network::RoomMember::State::Joined || - room_member->GetState() == Network::RoomMember::State::Moderator) { - + if (room_member->IsConnected()) { close(); } } diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index 4e1043053..e39dd1e0d 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -12,11 +12,15 @@ namespace Ui { class DirectConnect; } +namespace Core { +class System; +} + class DirectConnectWindow : public QDialog { Q_OBJECT public: - explicit DirectConnectWindow(Network::RoomNetwork& room_network_, QWidget* parent = nullptr); + explicit DirectConnectWindow(Core::System& system_, QWidget* parent = nullptr); ~DirectConnectWindow(); void RetranslateUi(); @@ -39,5 +43,6 @@ private: QFutureWatcher<void>* watcher; std::unique_ptr<Ui::DirectConnect> ui; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/direct_connect.ui b/src/yuzu/multiplayer/direct_connect.ui index 681b6bf69..57d6ec25a 100644 --- a/src/yuzu/multiplayer/direct_connect.ui +++ b/src/yuzu/multiplayer/direct_connect.ui @@ -83,7 +83,7 @@ <number>5</number> </property> <property name="placeholderText"> - <string>24872</string> + <string notr="true" extracomment="placeholder string that tells user default port">24872</string> </property> </widget> </item> diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index cb9464b2b..0c6adfd04 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -12,7 +12,9 @@ #include <QtConcurrent/QtConcurrentRun> #include "common/logging/log.h" #include "common/settings.h" -#include "core/announce_multiplayer_session.h" +#include "core/core.h" +#include "core/internal_network/network_interface.h" +#include "network/announce_multiplayer_session.h" #include "ui_host_room.h" #include "yuzu/game_list_p.h" #include "yuzu/main.h" @@ -27,10 +29,11 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list, std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Network::RoomNetwork& room_network_) + Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique<Ui::HostRoom>()), - announce_multiplayer_session(session), room_network{room_network_} { + announce_multiplayer_session(session), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); // set up validation for all of the fields @@ -105,6 +108,11 @@ std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBacken } void HostRoomWindow::Host() { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } if (!ui->username->hasAcceptableInput()) { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::USERNAME_NOT_VALID); return; @@ -121,6 +129,11 @@ void HostRoomWindow::Host() { NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::GAME_NOT_SELECTED); return; } + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } if (auto member = room_network.GetRoomMember().lock()) { if (member->GetState() == Network::RoomMember::State::Joining) { return; @@ -201,8 +214,8 @@ void HostRoomWindow::Host() { } #endif // TODO: Check what to do with this - member->Join(ui->username->text().toStdString(), "", "127.0.0.1", port, 0, - Network::NoPreferredMac, password, token); + member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0, + Network::NoPreferredIP, password, token); // Store settings UISettings::values.multiplayer_room_nickname = ui->username->text(); diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index a968042d0..034cb2eef 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -17,8 +17,9 @@ class HostRoom; } namespace Core { +class System; class AnnounceMultiplayerSession; -} +} // namespace Core class ConnectionError; class ComboBoxProxyModel; @@ -35,7 +36,7 @@ class HostRoomWindow : public QDialog { public: explicit HostRoomWindow(QWidget* parent, QStandardItemModel* list, std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Network::RoomNetwork& room_network_); + Core::System& system_); ~HostRoomWindow(); /** @@ -54,6 +55,7 @@ private: QStandardItemModel* game_list; ComboBoxProxyModel* proxy; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 23c2f21ab..107d40547 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -6,6 +6,8 @@ #include <QtConcurrent/QtConcurrentRun> #include "common/logging/log.h" #include "common/settings.h" +#include "core/core.h" +#include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_lobby.h" #include "yuzu/game_list_p.h" @@ -22,11 +24,11 @@ #endif Lobby::Lobby(QWidget* parent, QStandardItemModel* list, - std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Network::RoomNetwork& room_network_) + std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), ui(std::make_unique<Ui::Lobby>()), - announce_multiplayer_session(session), room_network{room_network_} { + announce_multiplayer_session(session), system{system_}, room_network{ + system.GetRoomNetwork()} { ui->setupUi(this); // setup the watcher for background connections @@ -114,6 +116,18 @@ void Lobby::OnExpandRoom(const QModelIndex& index) { } void Lobby::OnJoinRoom(const QModelIndex& source) { + if (!Network::GetSelectedNetworkInterface()) { + NetworkMessage::ErrorManager::ShowError( + NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); + return; + } + + if (system.IsPoweredOn()) { + if (!NetworkMessage::WarnGameRunning()) { + return; + } + } + if (const auto member = room_network.GetRoomMember().lock()) { // Prevent the user from trying to join a room while they are already joining. if (member->GetState() == Network::RoomMember::State::Joining) { @@ -169,7 +183,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { } #endif if (auto room_member = room_network.GetRoomMember().lock()) { - room_member->Join(nickname, "", ip.c_str(), port, 0, Network::NoPreferredMac, password, + room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredIP, password, token); } }); diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 82744ca94..2696aec21 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -9,7 +9,7 @@ #include <QSortFilterProxyModel> #include <QStandardItemModel> #include "common/announce_multiplayer_room.h" -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" #include "yuzu/multiplayer/validation.h" @@ -20,6 +20,10 @@ class Lobby; class LobbyModel; class LobbyFilterProxyModel; +namespace Core { +class System; +} + /** * Listing of all public games pulled from services. The lobby should be simple enough for users to * find the game they want to play, and join it. @@ -30,7 +34,7 @@ class Lobby : public QDialog { public: explicit Lobby(QWidget* parent, QStandardItemModel* list, std::shared_ptr<Core::AnnounceMultiplayerSession> session, - Network::RoomNetwork& room_network_); + Core::System& system_); ~Lobby() override; /** @@ -94,6 +98,7 @@ private: std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; QFutureWatcher<void>* watcher; Validation validation; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp index 76ec276ad..758b5b731 100644 --- a/src/yuzu/multiplayer/message.cpp +++ b/src/yuzu/multiplayer/message.cpp @@ -43,15 +43,15 @@ const ConnectionError ErrorManager::LOST_CONNECTION( QT_TR_NOOP("Connection to room lost. Try to reconnect.")); const ConnectionError ErrorManager::HOST_KICKED( QT_TR_NOOP("You have been kicked by the room host.")); -const ConnectionError ErrorManager::MAC_COLLISION( - QT_TR_NOOP("MAC address is already in use. Please choose another.")); -const ConnectionError ErrorManager::CONSOLE_ID_COLLISION(QT_TR_NOOP( - "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation " - "> Configure > System to regenerate your Console ID.")); +const ConnectionError ErrorManager::IP_COLLISION( + QT_TR_NOOP("IP address is already in use. Please choose another.")); const ConnectionError ErrorManager::PERMISSION_DENIED( QT_TR_NOOP("You do not have enough permission to perform this action.")); const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); +const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( + QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " + "make a selection.")); static bool WarnMessage(const std::string& title, const std::string& text) { return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), @@ -63,6 +63,13 @@ void ErrorManager::ShowError(const ConnectionError& e) { QMessageBox::critical(nullptr, tr("Error"), tr(e.GetString().c_str())); } +bool WarnGameRunning() { + return WarnMessage( + QT_TR_NOOP("Game already running"), + QT_TR_NOOP("Joining a room when the game is already running is discouraged " + "and can cause the room feature not to work correctly.\nProceed anyway?")); +} + bool WarnCloseRoom() { return WarnMessage( QT_TR_NOOP("Leave Room"), diff --git a/src/yuzu/multiplayer/message.h b/src/yuzu/multiplayer/message.h index eb5c8d1be..f038b9a1f 100644 --- a/src/yuzu/multiplayer/message.h +++ b/src/yuzu/multiplayer/message.h @@ -40,15 +40,23 @@ public: static const ConnectionError GENERIC_ERROR; static const ConnectionError LOST_CONNECTION; static const ConnectionError HOST_KICKED; - static const ConnectionError MAC_COLLISION; - static const ConnectionError CONSOLE_ID_COLLISION; + static const ConnectionError IP_COLLISION; static const ConnectionError PERMISSION_DENIED; static const ConnectionError NO_SUCH_USER; + static const ConnectionError NO_INTERFACE_SELECTED; /** * Shows a standard QMessageBox with a error message */ static void ShowError(const ConnectionError& e); }; + +/** + * Show a standard QMessageBox with a warning message about joining a room when + * the game is already running + * return true if the user wants to close the network connection + */ +bool WarnGameRunning(); + /** * Show a standard QMessageBox with a warning message about leaving the room * return true if the user wants to close the network connection diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index 4149b5232..66e098296 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -8,6 +8,7 @@ #include <QStandardItemModel> #include "common/announce_multiplayer_room.h" #include "common/logging/log.h" +#include "core/core.h" #include "yuzu/game_list.h" #include "yuzu/multiplayer/client_room.h" #include "yuzu/multiplayer/direct_connect.h" @@ -19,10 +20,9 @@ #include "yuzu/util/clickable_label.h" MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, - QAction* leave_room_, QAction* show_room_, - Network::RoomNetwork& room_network_) + QAction* leave_room_, QAction* show_room_, Core::System& system_) : QWidget(parent), game_list_model(game_list_model_), leave_room(leave_room_), - show_room(show_room_), room_network{room_network_} { + show_room(show_room_), system{system_}, room_network{system.GetRoomNetwork()} { if (auto member = room_network.GetRoomMember().lock()) { // register the network structs to use in slots and signals state_callback_handle = member->BindOnStateChanged( @@ -59,7 +59,9 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis }); } -MultiplayerState::~MultiplayerState() { +MultiplayerState::~MultiplayerState() = default; + +void MultiplayerState::Close() { if (state_callback_handle) { if (auto member = room_network.GetRoomMember().lock()) { member->Unbind(state_callback_handle); @@ -71,9 +73,6 @@ MultiplayerState::~MultiplayerState() { member->Unbind(error_callback_handle); } } -} - -void MultiplayerState::Close() { if (host_room) { host_room->close(); } @@ -95,7 +94,6 @@ void MultiplayerState::retranslateUi() { status_text->setText(tr("Not Connected. Click here to find a room!")); } else if (current_state == Network::RoomMember::State::Joined || current_state == Network::RoomMember::State::Moderator) { - status_text->setText(tr("Connected")); } else { status_text->setText(tr("Not Connected")); @@ -151,11 +149,8 @@ void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) { NetworkMessage::ErrorManager::ShowError( NetworkMessage::ErrorManager::USERNAME_NOT_VALID_SERVER); break; - case Network::RoomMember::Error::MacCollision: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::MAC_COLLISION); - break; - case Network::RoomMember::Error::ConsoleIdCollision: - NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::CONSOLE_ID_COLLISION); + case Network::RoomMember::Error::IpCollision: + NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_COLLISION); break; case Network::RoomMember::Error::RoomIsFull: NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::ROOM_IS_FULL); @@ -213,15 +208,14 @@ static void BringWidgetToFront(QWidget* widget) { void MultiplayerState::OnViewLobby() { if (lobby == nullptr) { - lobby = new Lobby(this, game_list_model, announce_multiplayer_session, room_network); + lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); } BringWidgetToFront(lobby); } void MultiplayerState::OnCreateRoom() { if (host_room == nullptr) { - host_room = - new HostRoomWindow(this, game_list_model, announce_multiplayer_session, room_network); + host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); } BringWidgetToFront(host_room); } @@ -284,7 +278,7 @@ void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnDirectConnectToRoom() { if (direct_connect == nullptr) { - direct_connect = new DirectConnectWindow(room_network, this); + direct_connect = new DirectConnectWindow(system, this); } BringWidgetToFront(direct_connect); } diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index 9c60712d5..c92496413 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -4,7 +4,7 @@ #pragma once #include <QWidget> -#include "core/announce_multiplayer_session.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" class QStandardItemModel; @@ -14,12 +14,16 @@ class ClientRoomWindow; class DirectConnectWindow; class ClickableLabel; +namespace Core { +class System; +} + class MultiplayerState : public QWidget { Q_OBJECT; public: explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, - QAction* show_room, Network::RoomNetwork& room_network_); + QAction* show_room, Core::System& system_); ~MultiplayerState(); /** @@ -86,6 +90,7 @@ private: Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; bool show_notification = false; + Core::System& system; Network::RoomNetwork& room_network; }; diff --git a/src/yuzu/multiplayer/validation.h b/src/yuzu/multiplayer/validation.h index 7d48e589d..dabf860be 100644 --- a/src/yuzu/multiplayer/validation.h +++ b/src/yuzu/multiplayer/validation.h @@ -10,7 +10,7 @@ class Validation { public: Validation() - : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, 65535) {} + : room_name(room_name_regex), nickname(nickname_regex), ip(ip_regex), port(0, UINT16_MAX) {} ~Validation() = default; diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 8421280bf..29b87da05 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -31,19 +31,36 @@ void CheckVulkan() { } } -bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +bool CheckEnvVars(bool* is_child) { #ifdef _WIN32 // Check environment variable to see if we are the child char variable_contents[8]; const DWORD startup_check_var = GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); - if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { + if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) { CheckVulkan(); return true; } + // Don't perform startup checks if we are a child process + char is_child_s[8]; + const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8); + if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) { + *is_child = true; + return false; + } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) { + std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", + IS_CHILD_ENV_VAR, GetLastError()); + return true; + } +#endif + return false; +} + +bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +#ifdef _WIN32 // Set the startup variable for child processes - const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); + const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); if (!env_var_set) { std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", STARTUP_CHECK_ENV_VAR, GetLastError()); @@ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { PROCESS_INFORMATION process_info; std::memset(&process_info, '\0', sizeof(process_info)); - if (!SpawnChild(arg0, &process_info)) { + if (!SpawnChild(arg0, &process_info, 0)) { return false; } @@ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { } #ifdef _WIN32 -bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { STARTUPINFOA startup_info; std::memset(&startup_info, '\0', sizeof(startup_info)); @@ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { nullptr, // lpProcessAttributes nullptr, // lpThreadAttributes false, // bInheritHandles - 0, // dwCreationFlags + flags, // dwCreationFlags nullptr, // lpEnvironment nullptr, // lpCurrentDirectory &startup_info, // lpStartupInfo diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h index 096dd54a8..f2fc2d9d4 100644 --- a/src/yuzu/startup_checks.h +++ b/src/yuzu/startup_checks.h @@ -7,11 +7,14 @@ #include <windows.h> #endif +constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD"; constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; +constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; void CheckVulkan(); +bool CheckEnvVars(bool* is_child); bool StartupChecks(const char* arg0, bool* has_broken_vulkan); #ifdef _WIN32 -bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); +bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); #endif diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index f683b80f7..2c1b547fb 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "yuzu/uisettings.h" diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 6cd4d6cb2..e12d414d9 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -1,6 +1,5 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2016 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -105,11 +104,12 @@ struct Values { // multiplayer settings Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; Settings::Setting<QString> multiplayer_ip{{}, "ip"}; - Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, 65535, "port"}; + Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; Settings::Setting<QString> multiplayer_room_name{{}, "room_name"}; Settings::SwitchableSetting<uint, true> multiplayer_max_player{8, 0, 8, "max_player"}; - Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, 65535, "room_port"}; + Settings::SwitchableSetting<uint, true> multiplayer_room_port{24872, 0, UINT16_MAX, + "room_port"}; Settings::SwitchableSetting<uint, true> multiplayer_host_type{0, 0, 1, "host_type"}; Settings::Setting<qulonglong> multiplayer_game_id{{}, "game_id"}; Settings::Setting<QString> multiplayer_room_description{{}, "room_description"}; diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp index bb5f74ec4..4b10fa517 100644 --- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <QDialogButtonBox> #include <QKeySequenceEdit> diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.h b/src/yuzu/util/sequence_dialog/sequence_dialog.h index 969c77740..85e146d40 100644 --- a/src/yuzu/util/sequence_dialog/sequence_dialog.h +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.h @@ -1,6 +1,5 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2018 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index ef31bc2d2..5c3e4589e 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include <array> #include <cmath> diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index e6790f260..39dd2d895 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.h @@ -1,6 +1,5 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. +// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc index 5733cac98..855df05fd 100644 --- a/src/yuzu/yuzu.qrc +++ b/src/yuzu/yuzu.qrc @@ -1,3 +1,8 @@ +<!-- +SPDX-FileCopyrightText: 2021 yuzu Emulator Project +SPDX-License-Identifier: GPL-2.0-or-later +--> + <RCC> <qresource prefix="/img"> <file alias="yuzu.ico">../../dist/yuzu.ico</file> diff --git a/src/yuzu/yuzu.rc b/src/yuzu/yuzu.rc index 4a3645a71..1fc74d065 100644 --- a/src/yuzu/yuzu.rc +++ b/src/yuzu/yuzu.rc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include "winresrc.h" ///////////////////////////////////////////////////////////////////////////// // |
