diff options
Diffstat (limited to 'src/yuzu')
37 files changed, 397 insertions, 132 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f6b389ede..50007338f 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -221,6 +221,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} @@ -229,7 +232,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 @@ -239,6 +248,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/aboutdialog.ui b/src/yuzu/aboutdialog.ui index c4ffb293e..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"> @@ -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_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/bootmanager.cpp b/src/yuzu/bootmanager.cpp index ef3bdfb1a..d3fbdb09d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -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(); @@ -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/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 58f1239bf..8ecd87150 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -73,7 +73,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}}, @@ -684,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); @@ -1300,6 +1301,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); 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 e16d127a8..04d397750 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -14,7 +14,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(); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index 64d68ab8f..42d30f170 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -4,7 +4,7 @@ #pragma once #include <memory> -#include <QWidget> +#include <QScrollArea> namespace Core { class System; @@ -14,7 +14,7 @@ namespace Ui { class ConfigureDebug; } -class ConfigureDebug : public QWidget { +class ConfigureDebug : public QScrollArea { Q_OBJECT public: diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 4c16274fc..47b8b80f1 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -1,7 +1,11 @@ <?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"> @@ -322,6 +326,7 @@ </item> </layout> </widget> + </widget> <tabstops> <tabstop>log_filter_edit</tabstop> <tabstop>toggle_console</tabstop> 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_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 00bee85b2..9e5a40fe7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -161,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 @@ -184,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", "")); @@ -362,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"), [&] { @@ -398,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)); }); @@ -1410,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 } @@ -1475,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_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_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 2e98ede8e..48f71b53c 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -219,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/game_list.cpp b/src/yuzu/game_list.cpp index 041e6ac11..b127badc2 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -126,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); @@ -149,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(); } /** @@ -286,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); @@ -333,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); @@ -753,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 f783283c9..cdf085019 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -140,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 e7667cf60..6198d1e4e 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -294,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); @@ -353,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/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 f8c234082..a85adc072 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -257,6 +257,18 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } +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(bool has_broken_vulkan) : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, @@ -274,6 +286,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(); @@ -473,8 +492,6 @@ GMainWindow::~GMainWindow() { delete render_window; } - system->GetRoomNetwork().Shutdown(); - #ifdef __linux__ ::close(sig_interrupt_fds[0]); ::close(sig_interrupt_fds[1]); @@ -843,7 +860,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 @@ -1075,7 +1092,7 @@ 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 current_volume = static_cast<s32>(Settings::values.volume.GetValue()); int step = 5; if (current_volume <= 30) { step = 2; @@ -1083,11 +1100,10 @@ void GMainWindow::InitializeHotkeys() { if (current_volume <= 6) { step = 1; } - const auto new_volume = std::max(current_volume - step, 0); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + 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 current_volume = static_cast<s32>(Settings::values.volume.GetValue()); int step = 5; if (current_volume < 30) { step = 2; @@ -1095,8 +1111,7 @@ void GMainWindow::InitializeHotkeys() { if (current_volume < 6) { step = 1; } - const auto new_volume = std::min(current_volume + step, 100); - Settings::values.volume.SetValue(static_cast<u8>(new_volume)); + 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()); @@ -1588,17 +1603,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, @@ -1632,11 +1648,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); @@ -3334,7 +3353,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); @@ -3809,6 +3829,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); + system->GetRoomNetwork().Shutdown(); QWidget::closeEvent(event); } @@ -3930,8 +3951,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); @@ -3939,6 +3973,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); @@ -3961,25 +4012,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()) { @@ -4022,6 +4057,26 @@ 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 @@ -4067,6 +4122,15 @@ 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"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 23b67a14e..1ae2b93d9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -218,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. @@ -251,6 +251,7 @@ private: bool ConfirmForceLockedExit(); void RequestGameExit(); void RequestGameResume(); + void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; #ifdef __linux__ @@ -347,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; @@ -392,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; 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/uisettings.h b/src/yuzu/uisettings.h index 25d1bf1e6..e12d414d9 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -104,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"}; |