diff options
Diffstat (limited to 'src/yuzu')
26 files changed, 315 insertions, 146 deletions
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index 1d8072243..12efdc216 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -291,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() { // Here, we check and validate the current configuration against all applicable parameters. const auto num_connected_players = static_cast<int>( std::count_if(player_groupboxes.begin(), player_groupboxes.end(), - [this](const QGroupBox* player) { return player->isChecked(); })); + [](const QGroupBox* player) { return player->isChecked(); })); const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index d3fbdb09d..24251247d 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -47,7 +47,7 @@ EmuThread::EmuThread(Core::System& system_) : system{system_} {} EmuThread::~EmuThread() = default; void EmuThread::run() { - std::string name = "yuzu:EmuControlThread"; + std::string name = "EmuControlThread"; MicroProfileOnThreadCreate(name.c_str()); Common::SetCurrentThreadName(name.c_str()); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index a4ed68422..195074bf2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -546,6 +546,7 @@ void Config::ReadDebuggingValues() { ReadBasicSetting(Settings::values.use_auto_stub); ReadBasicSetting(Settings::values.enable_all_controllers); ReadBasicSetting(Settings::values.create_crash_dumps); + ReadBasicSetting(Settings::values.perform_vulkan_check); qt_config->endGroup(); } @@ -1162,6 +1163,7 @@ void Config::SaveDebuggingValues() { WriteBasicSetting(Settings::values.disable_macro_jit); WriteBasicSetting(Settings::values.enable_all_controllers); WriteBasicSetting(Settings::values.create_crash_dumps); + WriteBasicSetting(Settings::values.perform_vulkan_check); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 622808e94..dacc75a20 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -77,6 +77,7 @@ void ConfigureDebug::SetConfiguration() { ui->disable_loop_safety_checks->setChecked( Settings::values.disable_shader_loop_safety_checks.GetValue()); ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); + ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); @@ -117,6 +118,7 @@ void ConfigureDebug::ApplyConfiguration() { ui->disable_loop_safety_checks->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); Settings::values.extended_logging = ui->extended_logging->isChecked(); + Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked(); UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked(); Debugger::ToggleConsole(); Common::Log::Filter filter; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 314d47af5..102c8c66c 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -313,6 +313,16 @@ </property> </widget> </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="perform_vulkan_check"> + <property name="toolTip"> + <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string> + </property> + <property name="text"> + <string>Perform Startup Vulkan Check</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 87e5d0f48..bd69d04a6 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -57,9 +57,10 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren UpdateBackgroundColorButton(new_bg_color); }); - ui->api->setEnabled(!UISettings::values.has_broken_vulkan); - ui->api_widget->setEnabled(!UISettings::values.has_broken_vulkan || - Settings::IsConfiguringGlobal()); + ui->api->setEnabled(!UISettings::values.has_broken_vulkan && ui->api->isEnabled()); + ui->api_widget->setEnabled( + (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) && + ui->api_widget->isEnabled()); ui->bg_label->setVisible(Settings::IsConfiguringGlobal()); ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal()); } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index cb55472c9..1db374d4a 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -163,10 +163,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, [this, input_subsystem, &hid_core] { CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); }); - connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, - [this, input_subsystem, &hid_core] { - CallConfigureDialog<ConfigureCamera>(*this, input_subsystem); - }); + connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] { + CallConfigureDialog<ConfigureCamera>(*this, input_subsystem); + }); connect(ui->vibrationButton, &QPushButton::clicked, [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 807afbeb2..9bb69cab1 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -67,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() { profile_names.push_back(profile_name); } + std::stable_sort(profile_names.begin(), profile_names.end()); + return profile_names; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 3c1bd19db..f45a25410 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3,6 +3,7 @@ #include <cinttypes> #include <clocale> +#include <cmath> #include <memory> #include <thread> #ifdef __APPLE__ @@ -105,12 +106,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/hle/kernel/k_process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" #include "input_common/drivers/tas_input.h" +#include "input_common/drivers/virtual_amiibo.h" #include "input_common/main.h" #include "ui_main.h" #include "util/overlay_dialog.h" @@ -261,6 +262,18 @@ static QString PrettyProductName() { return QSysInfo::prettyProductName(); } +#ifdef _WIN32 +static void OverrideWindowsFont() { + // Qt5 chooses these fonts on Windows and they have fairly ugly alphanumeric/cyrllic characters + // Asking to use "MS Shell Dlg 2" gives better other chars while leaving the Chinese Characters. + const QString startup_font = QApplication::font().family(); + const QStringList ugly_fonts = {QStringLiteral("SimSun"), QStringLiteral("PMingLiU")}; + if (ugly_fonts.contains(startup_font)) { + QApplication::setFont(QFont(QStringLiteral("MS Shell Dlg 2"), 9, QFont::Normal)); + } +} +#endif + bool GMainWindow::CheckDarkMode() { #ifdef __linux__ const QPalette test_palette(qApp->palette()); @@ -899,8 +912,8 @@ void GMainWindow::InitializeWidgets() { } // TODO (flTobi): Add the widget when multiplayer is fully implemented - // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); - // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); + statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); + statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); tas_label = new QLabel(); tas_label->setObjectName(QStringLiteral("TASlabel")); @@ -1299,6 +1312,7 @@ void GMainWindow::ConnectMenuEvents() { &MultiplayerState::OnDirectConnectToRoom); connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state, &MultiplayerState::OnOpenNetworkRoom); + connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig); // Tools connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, @@ -1339,6 +1353,8 @@ void GMainWindow::UpdateMenuState() { } else { ui->action_Pause->setText(tr("&Pause")); } + + multiplayer_state->UpdateNotificationStatus(); } void GMainWindow::OnDisplayTitleBars(bool show) { @@ -2000,7 +2016,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src } void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { - const QString entry_type = [this, type] { + const QString entry_type = [type] { switch (type) { case InstalledEntryType::Game: return tr("Contents"); @@ -2097,7 +2113,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, const std::string& game_path) { - const QString question = [this, target] { + const QString question = [target] { switch (target) { case GameListRemoveTarget::GlShaderCache: return tr("Delete OpenGL Transferable Shader Cache?"); @@ -2770,6 +2786,11 @@ void GMainWindow::OnExit() { OnStopGame(); } +void GMainWindow::OnSaveConfig() { + system->ApplySettings(); + config->Save(); +} + void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); @@ -3211,21 +3232,16 @@ void GMainWindow::OnLoadAmiibo() { return; } - Service::SM::ServiceManager& sm = system->ServiceManager(); - auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user"); - if (nfc == nullptr) { - QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos")); - return; - } - const auto nfc_state = nfc->GetCurrentState(); - if (nfc_state == Service::NFP::DeviceState::TagFound || - nfc_state == Service::NFP::DeviceState::TagMounted) { - nfc->CloseAmiibo(); + auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); + + // Remove amiibo if one is connected + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; } - if (nfc_state != Service::NFP::DeviceState::SearchingForTag) { + if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) { QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos")); return; } @@ -3244,24 +3260,30 @@ void GMainWindow::OnLoadAmiibo() { } void GMainWindow::LoadAmiibo(const QString& filename) { - Service::SM::ServiceManager& sm = system->ServiceManager(); - auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user"); - if (nfc == nullptr) { - return; - } - + auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); + const QString title = tr("Error loading Amiibo data"); // Remove amiibo if one is connected - const auto nfc_state = nfc->GetCurrentState(); - if (nfc_state == Service::NFP::DeviceState::TagFound || - nfc_state == Service::NFP::DeviceState::TagMounted) { - nfc->CloseAmiibo(); + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; } - if (!nfc->LoadAmiibo(filename.toStdString())) { - QMessageBox::warning(this, tr("Error loading Amiibo data"), - tr("Unable to load Amiibo data.")); + switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) { + case InputCommon::VirtualAmiibo::Info::NotAnAmiibo: + QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo")); + break; + case InputCommon::VirtualAmiibo::Info::UnableToLoad: + QMessageBox::warning(this, title, tr("The selected file is already on use")); + break; + case InputCommon::VirtualAmiibo::Info::WrongDeviceState: + QMessageBox::warning(this, title, tr("The current game is not looking for amiibos")); + break; + case InputCommon::VirtualAmiibo::Info::Unknown: + QMessageBox::warning(this, title, tr("An unkown error occured")); + break; + default: + break; } } @@ -3442,9 +3464,10 @@ void GMainWindow::UpdateStatusBar() { } if (!Settings::values.use_speed_limit) { game_fps_label->setText( - tr("Game: %1 FPS (Unlocked)").arg(results.average_game_fps, 0, 'f', 0)); + tr("Game: %1 FPS (Unlocked)").arg(std::round(results.average_game_fps), 0, 'f', 0)); } else { - game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0)); + game_fps_label->setText( + tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0)); } emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); @@ -4086,7 +4109,8 @@ int main(int argc, char* argv[]) { } #endif - if (StartupChecks(argv[0], &has_broken_vulkan)) { + if (StartupChecks(argv[0], &has_broken_vulkan, + Settings::values.perform_vulkan_check.GetValue())) { return 0; } @@ -4125,6 +4149,10 @@ int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); +#ifdef _WIN32 + OverrideWindowsFont(); +#endif + // 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 diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 716aef063..f7aa8e417 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -169,6 +169,7 @@ public slots: void OnLoadComplete(); void OnExecuteProgram(std::size_t program_index); void OnExit(); + void OnSaveConfig(); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); void SoftwareKeyboardInitialize( diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index cdf31b417..74d49dbd4 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -120,6 +120,20 @@ <addaction name="menu_Reset_Window_Size"/> <addaction name="menu_View_Debugging"/> </widget> + <widget class="QMenu" name="menu_Multiplayer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>&Multiplayer</string> + </property> + <addaction name="action_View_Lobby"/> + <addaction name="action_Start_Room"/> + <addaction name="action_Connect_To_Room"/> + <addaction name="separator"/> + <addaction name="action_Show_Room"/> + <addaction name="action_Leave_Room"/> + </widget> <widget class="QMenu" name="menu_Tools"> <property name="title"> <string>&Tools</string> @@ -251,7 +265,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Browse Public Game Lobby</string> + <string>&Browse Public Game Lobby</string> </property> </action> <action name="action_Start_Room"> @@ -259,7 +273,7 @@ <bool>true</bool> </property> <property name="text"> - <string>Create Room</string> + <string>&Create Room</string> </property> </action> <action name="action_Leave_Room"> @@ -267,12 +281,12 @@ <bool>false</bool> </property> <property name="text"> - <string>Leave Room</string> + <string>&Leave Room</string> </property> </action> <action name="action_Connect_To_Room"> <property name="text"> - <string>Direct Connect to Room</string> + <string>&Direct Connect to Room</string> </property> </action> <action name="action_Show_Room"> @@ -280,7 +294,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Show Current Room</string> + <string>&Show Current Room</string> </property> </action> <action name="action_Fullscreen"> diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index 9e672f82e..dec9696c1 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -61,7 +61,10 @@ public: /// Format the message using the players color QString GetPlayerChatMessage(u16 player) const { - auto color = player_color[player % 16]; + const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight")); + auto color = + is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16]; QString name; if (username.isEmpty() || username == nickname) { name = nickname; @@ -84,9 +87,12 @@ public: } private: - static constexpr std::array<const char*, 16> player_color = { + static constexpr std::array<const char*, 16> player_color_default = { {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", - "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}}; + "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}}; + static constexpr std::array<const char*, 16> player_color_dark = { + {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733", + "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}}; static constexpr char ping_color[] = "#FFFF00"; QString timestamp; diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index b34a8d004..caf34a414 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() { auto memberlist = member->GetMemberInformation(); ui->chat->SetPlayerList(memberlist); const auto information = member->GetRoomInformation(); - setWindowTitle(QString(tr("%1 (%2/%3 members) - connected")) + setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected")) .arg(QString::fromStdString(information.name)) + .arg(QString::fromStdString(information.preferred_game.name)) .arg(memberlist.size()) .arg(information.member_slots)); ui->description->setText(QString::fromStdString(information.description)); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 017063074..10bf0a4fb 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() { UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); } + emit SaveConfig(); + // attempt to connect in a different thread QFuture<void> f = QtConcurrent::run([&] { if (auto room_member = room_network.GetRoomMember().lock()) { diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h index e39dd1e0d..b8f66cfb2 100644 --- a/src/yuzu/multiplayer/direct_connect.h +++ b/src/yuzu/multiplayer/direct_connect.h @@ -31,6 +31,7 @@ signals: * connections that it might have. */ void Closed(); + void SaveConfig(); private slots: void OnConnection(); diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 0c6adfd04..a8faa5b24 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -232,6 +232,7 @@ void HostRoomWindow::Host() { } UISettings::values.multiplayer_room_description = ui->room_description->toPlainText(); ui->host->setEnabled(true); + emit SaveConfig(); close(); } } diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h index 034cb2eef..ae816e2e0 100644 --- a/src/yuzu/multiplayer/host_room.h +++ b/src/yuzu/multiplayer/host_room.h @@ -46,6 +46,9 @@ public: void UpdateGameList(QStandardItemModel* list); void RetranslateUi(); +signals: + void SaveConfig(); + private: void Host(); std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const; diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 107d40547..08c275696 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/internal_network/network_interface.h" #include "network/network.h" #include "ui_lobby.h" @@ -26,9 +27,9 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, 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), system{system_}, room_network{ - system.GetRoomNetwork()} { + ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session), + profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_}, + room_network{system.GetRoomNetwork()} { ui->setupUi(this); // setup the watcher for background connections @@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, ui->nickname->setValidator(validation.GetNickname()); ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue()); - if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { - // Use yuzu Web Service user name as nickname by default - ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); + + // Try find the best nickname by default + if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) { + if (!Settings::values.yuzu_username.GetValue().empty()) { + ui->nickname->setText( + QString::fromStdString(Settings::values.yuzu_username.GetValue())); + } else if (!GetProfileUsername().empty()) { + ui->nickname->setText(QString::fromStdString(GetProfileUsername())); + } else { + ui->nickname->setText(QStringLiteral("yuzu")); + } } // UI Buttons @@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, // Actions connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, &Lobby::OnRefreshLobby); - - // manually start a refresh when the window is opening - // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as - // part of the constructor, but offload the refresh until after the window shown. perhaps emit a - // refreshroomlist signal from places that open the lobby - RefreshLobby(); } Lobby::~Lobby() = default; @@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) { } if (proxy) proxy->UpdateGameList(game_list); + ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); } void Lobby::RetranslateUi() { @@ -117,6 +121,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) { void Lobby::OnJoinRoom(const QModelIndex& source) { if (!Network::GetSelectedNetworkInterface()) { + LOG_INFO(WebService, "Automatically selected network interface for room network."); + Network::SelectFirstNetworkInterface(); + } + + if (!Network::GetSelectedNetworkInterface()) { NetworkMessage::ErrorManager::ShowError( NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); return; @@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); UISettings::values.multiplayer_port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); + emit SaveConfig(); } void Lobby::ResetModel() { model->clear(); model->insertColumns(0, Column::TOTAL); - model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole); + model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); - model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); } void Lobby::RefreshLobby() { @@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() { for (int r = 0; r < game_list->rowCount(); ++r) { auto index = game_list->index(r, 0); auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); + if (game_id != 0 && room.information.preferred_game.id == game_id) { smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>(); } @@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() { members.append(var); } - auto first_item = new LobbyItem(); + auto first_item = new LobbyItemGame( + room.information.preferred_game.id, + QString::fromStdString(room.information.preferred_game.name), smdh_icon); auto row = QList<QStandardItem*>({ first_item, new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), - new LobbyItemGame(room.information.preferred_game.id, - QString::fromStdString(room.information.preferred_game.name), - smdh_icon), + new LobbyItemMemberList(members, room.information.member_slots), new LobbyItemHost(QString::fromStdString(room.information.host_username), QString::fromStdString(room.ip), room.information.port, QString::fromStdString(room.verify_uid)), - new LobbyItemMemberList(members, room.information.member_slots), }); model->appendRow(row); // To make the rows expandable, add the member data as a child of the first column of the @@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() { ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); } } + + ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder); +} + +std::string Lobby::GetProfileUsername() { + const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue()); + Service::Account::ProfileBase profile{}; + + if (!current_user.has_value()) { + return ""; + } + + if (!profile_manager->GetProfileBase(*current_user, profile)) { + return ""; + } + + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + + return text; } LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h index 2696aec21..300dad13e 100644 --- a/src/yuzu/multiplayer/lobby.h +++ b/src/yuzu/multiplayer/lobby.h @@ -24,6 +24,10 @@ namespace Core { class System; } +namespace Service::Account { +class ProfileManager; +} + /** * 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. @@ -75,8 +79,11 @@ private slots: signals: void StateChanged(const Network::RoomMember::State&); + void SaveConfig(); private: + std::string GetProfileUsername(); + /** * Removes all entries in the Lobby before refreshing. */ @@ -96,6 +103,7 @@ private: QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher; std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; + std::unique_ptr<Service::Account::ProfileManager> profile_manager; QFutureWatcher<void>* watcher; Validation validation; Core::System& system; diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h index 8071cede4..068c95aca 100644 --- a/src/yuzu/multiplayer/lobby_p.h +++ b/src/yuzu/multiplayer/lobby_p.h @@ -11,11 +11,10 @@ namespace Column { enum List { - EXPAND, - ROOM_NAME, GAME_NAME, - HOST, + ROOM_NAME, MEMBER, + HOST, TOTAL, }; } @@ -91,6 +90,8 @@ public: setData(game_name, GameNameRole); if (!smdh_icon.isNull()) { setData(smdh_icon, GameIconRole); + } else { + setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole); } } @@ -98,7 +99,12 @@ public: if (role == Qt::DecorationRole) { auto val = data(GameIconRole); if (val.isValid()) { - val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio); + val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio, + Qt::TransformationMode::SmoothTransformation); + } else { + auto blank_image = QPixmap(32, 32); + blank_image.fill(Qt::black); + val = blank_image; } return val; } else if (role != Qt::DisplayRole) { @@ -191,8 +197,8 @@ public: return LobbyItem::data(role); } auto members = data(MemberListRole).toList(); - return QStringLiteral("%1 / %2").arg(QString::number(members.size()), - data(MaxPlayerRole).toString()); + return QStringLiteral("%1 / %2 ") + .arg(QString::number(members.size()), data(MaxPlayerRole).toString()); } bool operator<(const QStandardItem& other) const override { diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp index 758b5b731..6d8f18274 100644 --- a/src/yuzu/multiplayer/message.cpp +++ b/src/yuzu/multiplayer/message.cpp @@ -49,9 +49,9 @@ 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.")); +const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP( + "No valid 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()), diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index 66e098296..ae2738ad4 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis status_text = new ClickableLabel(this); status_icon = new ClickableLabel(this); - status_text->setToolTip(tr("Current connection status")); - status_text->setText(tr("Not Connected. Click here to find a room!")); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); @@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis HideNotification(); } }); + + retranslateUi(); } MultiplayerState::~MultiplayerState() = default; @@ -90,14 +89,7 @@ void MultiplayerState::Close() { void MultiplayerState::retranslateUi() { status_text->setToolTip(tr("Current connection status")); - if (current_state == Network::RoomMember::State::Uninitialized) { - 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")); - } + UpdateNotificationStatus(); if (lobby) { lobby->RetranslateUi(); @@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() { } } +void MultiplayerState::SetNotificationStatus(NotificationStatus status) { + notification_status = status; + UpdateNotificationStatus(); +} + +void MultiplayerState::UpdateNotificationStatus() { + switch (notification_status) { + case NotificationStatus::Unitialized: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); + status_text->setText(tr("Not Connected. Click here to find a room!")); + leave_room->setEnabled(false); + show_room->setEnabled(false); + break; + case NotificationStatus::Disconnected: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); + status_text->setText(tr("Not Connected")); + leave_room->setEnabled(false); + show_room->setEnabled(false); + break; + case NotificationStatus::Connected: + status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); + status_text->setText(tr("Connected")); + leave_room->setEnabled(true); + show_room->setEnabled(true); + break; + case NotificationStatus::Notification: + status_icon->setPixmap( + QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); + status_text->setText(tr("New Messages Received")); + leave_room->setEnabled(true); + show_room->setEnabled(true); + break; + } + + // Clean up status bar if game is running + if (system.IsPoweredOn()) { + status_text->clear(); + } +} + void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); if (state == Network::RoomMember::State::Joined || state == Network::RoomMember::State::Moderator) { OnOpenNetworkRoom(); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - status_text->setText(tr("Connected")); - leave_room->setEnabled(true); - show_room->setEnabled(true); + SetNotificationStatus(NotificationStatus::Connected); } else { - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); - status_text->setText(tr("Not Connected")); - leave_room->setEnabled(false); - show_room->setEnabled(false); + SetNotificationStatus(NotificationStatus::Disconnected); } current_state = state; @@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) { QMessageBox::Ok); } +void MultiplayerState::OnSaveConfig() { + emit SaveConfig(); +} + void MultiplayerState::UpdateThemedIcons() { if (show_notification) { status_icon->setPixmap( @@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) { void MultiplayerState::OnViewLobby() { if (lobby == nullptr) { lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); + connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig); } + lobby->RefreshLobby(); BringWidgetToFront(lobby); } void MultiplayerState::OnCreateRoom() { if (host_room == nullptr) { host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); + connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig); } BringWidgetToFront(host_room); } @@ -249,14 +282,13 @@ void MultiplayerState::ShowNotification() { return; // Do not show notification if the chat window currently has focus show_notification = true; QApplication::alert(nullptr); - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); - status_text->setText(tr("New Messages Received")); + QApplication::beep(); + SetNotificationStatus(NotificationStatus::Notification); } void MultiplayerState::HideNotification() { show_notification = false; - status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); - status_text->setText(tr("Connected")); + SetNotificationStatus(NotificationStatus::Connected); } void MultiplayerState::OnOpenNetworkRoom() { @@ -279,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnDirectConnectToRoom() { if (direct_connect == nullptr) { direct_connect = new DirectConnectWindow(system, this); + connect(direct_connect, &DirectConnectWindow::SaveConfig, this, + &MultiplayerState::OnSaveConfig); } BringWidgetToFront(direct_connect); } diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h index c92496413..5d681c5c6 100644 --- a/src/yuzu/multiplayer/state.h +++ b/src/yuzu/multiplayer/state.h @@ -22,6 +22,13 @@ class MultiplayerState : public QWidget { Q_OBJECT; public: + enum class NotificationStatus { + Unitialized, + Disconnected, + Connected, + Notification, + }; + explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, QAction* show_room, Core::System& system_); ~MultiplayerState(); @@ -31,6 +38,10 @@ public: */ void Close(); + void SetNotificationStatus(NotificationStatus state); + + void UpdateNotificationStatus(); + ClickableLabel* GetStatusText() const { return status_text; } @@ -64,6 +75,7 @@ public slots: void OnOpenNetworkRoom(); void OnDirectConnectToRoom(); void OnAnnounceFailed(const WebService::WebResult&); + void OnSaveConfig(); void UpdateThemedIcons(); void ShowNotification(); void HideNotification(); @@ -72,6 +84,7 @@ signals: void NetworkStateChanged(const Network::RoomMember::State&); void NetworkError(const Network::RoomMember::Error&); void AnnounceFailed(const WebService::WebResult&); + void SaveConfig(); private: Lobby* lobby = nullptr; @@ -85,6 +98,7 @@ private: QAction* show_room; std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; + NotificationStatus notification_status = NotificationStatus::Unitialized; bool has_mod_perms = false; Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 29b87da05..fc2693f9d 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -57,7 +57,7 @@ bool CheckEnvVars(bool* is_child) { return false; } -bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) { #ifdef _WIN32 // Set the startup variable for child processes const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); @@ -67,29 +67,32 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { return false; } - PROCESS_INFORMATION process_info; - std::memset(&process_info, '\0', sizeof(process_info)); - - if (!SpawnChild(arg0, &process_info, 0)) { - return false; - } - - // Wait until the processs exits and get exit code from it - WaitForSingleObject(process_info.hProcess, INFINITE); - DWORD exit_code = STILL_ACTIVE; - const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); - if (err == 0) { - std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); - } - - // Vulkan is broken if the child crashed (return value is not zero) - *has_broken_vulkan = (exit_code != 0); - - if (CloseHandle(process_info.hProcess) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); - } - if (CloseHandle(process_info.hThread) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + if (perform_vulkan_check) { + // Spawn child process that performs Vulkan check + PROCESS_INFORMATION process_info; + std::memset(&process_info, '\0', sizeof(process_info)); + + if (!SpawnChild(arg0, &process_info, 0)) { + return false; + } + + // Wait until the processs exits and get exit code from it + WaitForSingleObject(process_info.hProcess, INFINITE); + DWORD exit_code = STILL_ACTIVE; + const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); + if (err == 0) { + std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); + } + + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (exit_code != 0); + + if (CloseHandle(process_info.hProcess) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } + if (CloseHandle(process_info.hThread) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } } if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) { @@ -98,26 +101,28 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { } #elif defined(YUZU_UNIX) - const pid_t pid = fork(); - if (pid == 0) { - CheckVulkan(); - return true; - } else if (pid == -1) { - const int err = errno; - std::fprintf(stderr, "fork failed with error %d\n", err); - return false; - } - - // Get exit code from child process - int status; - const int r_val = wait(&status); - if (r_val == -1) { - const int err = errno; - std::fprintf(stderr, "wait failed with error %d\n", err); - return false; + if (perform_vulkan_check) { + const pid_t pid = fork(); + if (pid == 0) { + CheckVulkan(); + return true; + } else if (pid == -1) { + const int err = errno; + std::fprintf(stderr, "fork failed with error %d\n", err); + return false; + } + + // Get exit code from child process + int status; + const int r_val = wait(&status); + if (r_val == -1) { + const int err = errno; + std::fprintf(stderr, "wait failed with error %d\n", err); + return false; + } + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (status != 0); } - // Vulkan is broken if the child crashed (return value is not zero) - *has_broken_vulkan = (status != 0); #endif return false; } diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h index f2fc2d9d4..d8e563be6 100644 --- a/src/yuzu/startup_checks.h +++ b/src/yuzu/startup_checks.h @@ -13,7 +13,7 @@ constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; void CheckVulkan(); bool CheckEnvVars(bool* is_child); -bool StartupChecks(const char* arg0, bool* has_broken_vulkan); +bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check); #ifdef _WIN32 bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index e12d414d9..753797efc 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -102,7 +102,7 @@ struct Values { Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"}; // multiplayer settings - Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; + Settings::Setting<QString> multiplayer_nickname{{}, "nickname"}; Settings::Setting<QString> multiplayer_ip{{}, "ip"}; Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; |