From b5a0695783163b3bf4416584e922074f84e6171d Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:12:57 +0000 Subject: [PATCH 1/7] Edit game_list.h --- src/citron/game_list.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/citron/game_list.h b/src/citron/game_list.h index f78937d83..781cd9489 100644 --- a/src/citron/game_list.h +++ b/src/citron/game_list.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,7 @@ #include "citron/play_time_manager.h" namespace Core { -class System; + class System; } class ControllerNavigation; @@ -35,8 +36,8 @@ enum class AmLaunchType; enum class StartGameType; namespace FileSys { -class ManualContentProvider; -class VfsFilesystem; + class ManualContentProvider; + class VfsFilesystem; } // namespace FileSys enum class GameListOpenTarget { @@ -79,6 +80,7 @@ public: COLUMN_FILE_TYPE, COLUMN_SIZE, COLUMN_PLAY_TIME, + COLUMN_ONLINE, COLUMN_COUNT, // Number of columns }; @@ -139,6 +141,7 @@ private slots: void OnTextChanged(const QString& new_text); void OnFilterCloseClicked(); void OnUpdateThemedIcons(); + void UpdateOnlineStatus(); private: friend class GameListWorker; @@ -183,6 +186,7 @@ private: QFileSystemWatcher* watcher = nullptr; ControllerNavigation* controller_navigation = nullptr; CompatibilityList compatibility_list; + QTimer* online_status_timer; friend class GameListSearchField; From 6c519e922ded3889c1a1aba84517db6b2e2b4f05 Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:13:58 +0000 Subject: [PATCH 2/7] Edit game_list.cpp --- src/citron/game_list.cpp | 646 +++++++++++++++++++++------------------ 1 file changed, 349 insertions(+), 297 deletions(-) diff --git a/src/citron/game_list.cpp b/src/citron/game_list.cpp index 6360ac512..4d9f6e357 100644 --- a/src/citron/game_list.cpp +++ b/src/citron/game_list.cpp @@ -30,7 +30,7 @@ #include "citron/util/controller_navigation.h" GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) - : QObject(parent), gamelist{gamelist_} {} +: QObject(parent), gamelist{gamelist_} {} // EventFilter in order to process systemkeys while editing the searchfield bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { @@ -45,36 +45,36 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve // If no function key changes the searchfield's text the filter doesn't need to get reloaded if (edit_filter_text == edit_filter_text_old) { switch (keyEvent->key()) { - // Escape: Resets the searchfield - case Qt::Key_Escape: { - if (edit_filter_text_old.isEmpty()) { - return QObject::eventFilter(obj, event); - } else { - gamelist->search_field->edit_filter->clear(); - edit_filter_text.clear(); + // Escape: Resets the searchfield + case Qt::Key_Escape: { + if (edit_filter_text_old.isEmpty()) { + return QObject::eventFilter(obj, event); + } else { + gamelist->search_field->edit_filter->clear(); + edit_filter_text.clear(); + } + break; } - break; - } - // Return and Enter - // If the enter key gets pressed first checks how many and which entry is visible - // If there is only one result launch this game - case Qt::Key_Return: - case Qt::Key_Enter: { - if (gamelist->search_field->visible == 1) { - const QString file_path = gamelist->GetLastFilterResultItem(); + // Return and Enter + // If the enter key gets pressed first checks how many and which entry is visible + // If there is only one result launch this game + case Qt::Key_Return: + case Qt::Key_Enter: { + if (gamelist->search_field->visible == 1) { + const QString file_path = gamelist->GetLastFilterResultItem(); - // To avoid loading error dialog loops while confirming them using enter - // Also users usually want to run a different game after closing one - gamelist->search_field->edit_filter->clear(); - edit_filter_text.clear(); - emit gamelist->GameChosen(file_path); - } else { - return QObject::eventFilter(obj, event); + // To avoid loading error dialog loops while confirming them using enter + // Also users usually want to run a different game after closing one + gamelist->search_field->edit_filter->clear(); + edit_filter_text.clear(); + emit gamelist->GameChosen(file_path); + } else { + return QObject::eventFilter(obj, event); + } + break; } - break; - } - default: - return QObject::eventFilter(obj, event); + default: + return QObject::eventFilter(obj, event); } } edit_filter_text_old = edit_filter_text; @@ -139,9 +139,9 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { button_filter_close->setCursor(Qt::ArrowCursor); button_filter_close->setStyleSheet( QStringLiteral("QToolButton{ border: none; padding: 0px; color: " - "#000000; font-weight: bold; background: #F0F0F0; }" - "QToolButton:hover{ border: none; padding: 0px; color: " - "#EEEEEE; font-weight: bold; background: #E81123}")); + "#000000; font-weight: bold; background: #F0F0F0; }" + "QToolButton:hover{ border: none; padding: 0px; color: " + "#EEEEEE; font-weight: bold; background: #E81123}")); connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked); layout_filter->setSpacing(10); layout_filter->addWidget(label_filter); @@ -171,8 +171,8 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput) void GameList::OnItemExpanded(const QModelIndex& item) { const auto type = item.data(GameListItem::TypeRole).value(); const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || - type == GameListItemType::UserNandDir || - type == GameListItemType::SysNandDir; + type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir; const bool is_fave = type == GameListItemType::Favorites; if (!is_dir && !is_fave) { return; @@ -247,7 +247,7 @@ void GameList::FilterGridView(const QString& filter_text) { const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title; should_show = ContainsAllWords(file_name, filter_text) || - (file_program_id.size() == 16 && file_program_id.contains(filter_text)); + (file_program_id.size() == 16 && file_program_id.contains(filter_text)); } if (should_show) { @@ -260,7 +260,7 @@ void GameList::FilterGridView(const QString& filter_text) { // Fallback to filename if no title std::string filename; Common::SplitPath(game_item->data(GameListItemPath::FullPathRole).toString().toStdString(), - nullptr, &filename, nullptr); + nullptr, &filename, nullptr); game_title = QString::fromStdString(filename); } cloned_item->setText(game_title); @@ -316,11 +316,11 @@ void GameList::FilterTreeView(const QString& filter_text) { const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong(); const QString file_path = - child->data(GameListItemPath::FullPathRole).toString().toLower(); + child->data(GameListItemPath::FullPathRole).toString().toLower(); const QString file_title = - child->data(GameListItemPath::TitleRole).toString().toLower(); + child->data(GameListItemPath::TitleRole).toString().toLower(); const QString file_program_id = - QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); + QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); // Only items which filename in combination with its title contains all words // that are in the searchfield will be visible in the gamelist @@ -328,15 +328,15 @@ void GameList::FilterTreeView(const QString& filter_text) { // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent // multiple conversions of edit_filter_text for each game in the gamelist const QString file_name = - file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + - file_title; + file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + + file_title; if (ContainsAllWords(file_name, filter_text) || (file_program_id.size() == 16 && file_program_id.contains(filter_text))) { tree_view->setRowHidden(j, folder_index, false); - ++result_count; - } else { - tree_view->setRowHidden(j, folder_index, true); - } + ++result_count; + } else { + tree_view->setRowHidden(j, folder_index, true); + } } } search_field->setFilterResult(result_count, children_total); @@ -350,55 +350,55 @@ void GameList::OnUpdateThemedIcons() { const int icon_size = UISettings::values.folder_icon_size.GetValue(); switch (child->data(GameListItem::TypeRole).value()) { - case GameListItemType::SdmcDir: - child->setData( - QIcon::fromTheme(QStringLiteral("sd_card")) + case GameListItemType::SdmcDir: + child->setData( + QIcon::fromTheme(QStringLiteral("sd_card")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::UserNandDir: - child->setData( - QIcon::fromTheme(QStringLiteral("chip")) + Qt::DecorationRole); + break; + case GameListItemType::UserNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::SysNandDir: - child->setData( - QIcon::fromTheme(QStringLiteral("chip")) + Qt::DecorationRole); + break; + case GameListItemType::SysNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::CustomDir: { - const UISettings::GameDir& game_dir = + Qt::DecorationRole); + break; + case GameListItemType::CustomDir: { + const UISettings::GameDir& game_dir = UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()]; - const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path)) - ? QStringLiteral("folder") - : QStringLiteral("bad_folder"); - child->setData( - QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( - icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - } - case GameListItemType::AddDir: - child->setData( - QIcon::fromTheme(QStringLiteral("list-add")) + const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path)) + ? QStringLiteral("folder") + : QStringLiteral("bad_folder"); + child->setData( + QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( + icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + } + case GameListItemType::AddDir: + child->setData( + QIcon::fromTheme(QStringLiteral("list-add")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::Favorites: - child->setData( - QIcon::fromTheme(QStringLiteral("star")) + Qt::DecorationRole); + break; + case GameListItemType::Favorites: + child->setData( + QIcon::fromTheme(QStringLiteral("star")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - default: - break; + Qt::DecorationRole); + break; + default: + break; } } } @@ -407,11 +407,11 @@ void GameList::OnFilterCloseClicked() { main_window->filterBarSetChecked(false); } -GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, +GameList::GameList(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, GMainWindow* parent) - : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, - play_time_manager{play_time_manager_}, system{system_} { +: QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, +play_time_manager{play_time_manager_}, system{system_} { watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -505,6 +505,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid // Set initial view mode SetViewMode(UISettings::values.game_list_grid_view.GetValue()); + + // Set up the timer for automatic refresh + online_status_timer = new QTimer(this); + connect(online_status_timer, &QTimer::timeout, this, &GameList::UpdateOnlineStatus); + online_status_timer->start(5000); // Refresh every 5 seconds } void GameList::UnloadController() { @@ -544,46 +549,92 @@ void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); tree_view->setExpanded( entry_items->index(), - UISettings::values.game_dirs[entry_items->data(GameListDir::GameDirRole).toInt()].expanded); + UISettings::values.game_dirs[entry_items->data(GameListDir::GameDirRole).toInt()].expanded); } void GameList::AddEntry(const QList& entry_items, GameListDir* parent) { parent->appendRow(entry_items); } +// THIS IS THE NEW REFRESH FUNCTION +void GameList::UpdateOnlineStatus() { + auto session = main_window->GetMultiplayerState()->GetSession(); + if (!session || !item_model) { + return; + } + + // This part is the same as in the worker: fetch and count. + std::map> online_stats; // Game ID -> {player_count, server_count} + AnnounceMultiplayerRoom::RoomList room_list = session->GetRoomList(); + for (const auto& room : room_list) { + u64 game_id = room.information.preferred_game.id; + if (game_id != 0) { + online_stats[game_id].first += room.members.size(); + online_stats[game_id].second++; + } + } + + // Now, iterate through the existing list and update the "Online" column. + for (int i = 0; i < item_model->rowCount(); ++i) { + QStandardItem* folder = item_model->item(i, 0); + if (!folder) continue; + + for (int j = 0; j < folder->rowCount(); ++j) { + QStandardItem* game_item = folder->child(j, COLUMN_NAME); + if (!game_item) continue; + + u64 program_id = game_item->data(GameListItemPath::ProgramIdRole).toULongLong(); + QString online_text = QStringLiteral("N/A"); + + auto it_stats = online_stats.find(program_id); + if (it_stats != online_stats.end()) { + const auto& stats = it_stats->second; + online_text = QStringLiteral("Players: %1 | Servers: %2").arg(stats.first).arg(stats.second); + } + + // This is the efficient update. We find the item for the "Online" column and just change its text. + QStandardItem* online_item = folder->child(j, COLUMN_ONLINE); + if (online_item) { + online_item->setData(online_text, Qt::DisplayRole); + } + } + } +} + + void GameList::ValidateEntry(const QModelIndex& item) { const auto selected = item.sibling(item.row(), 0); switch (selected.data(GameListItem::TypeRole).value()) { - case GameListItemType::Game: { - const QString file_path = selected.data(GameListItemPath::FullPathRole).toString(); - if (file_path.isEmpty()) - return; - const QFileInfo file_info(file_path); - if (!file_info.exists()) - return; + case GameListItemType::Game: { + const QString file_path = selected.data(GameListItemPath::FullPathRole).toString(); + if (file_path.isEmpty()) + return; + const QFileInfo file_info(file_path); + if (!file_info.exists()) + return; - if (file_info.isDir()) { - const QDir dir{file_path}; - const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); - if (matching_main.size() == 1) { - emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); + if (file_info.isDir()) { + const QDir dir{file_path}; + const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); + if (matching_main.size() == 1) { + emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); + } + return; } - return; + + const auto title_id = selected.data(GameListItemPath::ProgramIdRole).toULongLong(); + + // Users usually want to run a different game after closing one + search_field->clear(); + emit GameChosen(file_path, title_id); + break; } - - const auto title_id = selected.data(GameListItemPath::ProgramIdRole).toULongLong(); - - // Users usually want to run a different game after closing one - search_field->clear(); - emit GameChosen(file_path, title_id); - break; - } - case GameListItemType::AddDir: - emit AddDirectory(); - break; - default: - break; + case GameListItemType::AddDir: + emit AddDirectory(); + break; + default: + break; } } @@ -594,10 +645,10 @@ bool GameList::IsEmpty() const { if (!child->hasChildren() && (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || - type == GameListItemType::SysNandDir)) { + type == GameListItemType::SysNandDir)) { item_model->invisibleRootItem()->removeRow(child->row()); - i--; - } + i--; + } } return !item_model->invisibleRootItem()->hasChildren(); @@ -667,24 +718,24 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { const auto selected = item.sibling(item.row(), 0); QMenu context_menu; switch (selected.data(GameListItem::TypeRole).value()) { - case GameListItemType::Game: - AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(), - selected.data(GameListItemPath::FullPathRole).toString().toStdString()); - break; - case GameListItemType::CustomDir: - AddPermDirPopup(context_menu, selected); - AddCustomDirPopup(context_menu, selected); - break; - case GameListItemType::SdmcDir: - case GameListItemType::UserNandDir: - case GameListItemType::SysNandDir: - AddPermDirPopup(context_menu, selected); - break; - case GameListItemType::Favorites: - AddFavoritesPopup(context_menu); - break; - default: - break; + case GameListItemType::Game: + AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(), + selected.data(GameListItemPath::FullPathRole).toString().toStdString()); + break; + case GameListItemType::CustomDir: + AddPermDirPopup(context_menu, selected); + AddCustomDirPopup(context_menu, selected); + break; + case GameListItemType::SdmcDir: + case GameListItemType::UserNandDir: + case GameListItemType::SysNandDir: + AddPermDirPopup(context_menu, selected); + break; + case GameListItemType::Favorites: + AddFavoritesPopup(context_menu); + break; + default: + break; } if (tree_view->isVisible()) { @@ -699,12 +750,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri context_menu.addSeparator(); QAction* start_game = context_menu.addAction(tr("Start Game")); QAction* start_game_global = - context_menu.addAction(tr("Start Game without Custom Configuration")); + context_menu.addAction(tr("Start Game without Custom Configuration")); context_menu.addSeparator(); QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = - context_menu.addAction(tr("Open Transferable Pipeline Cache")); + context_menu.addAction(tr("Open Transferable Pipeline Cache")); context_menu.addSeparator(); QMenu* remove_menu = context_menu.addMenu(tr("Remove")); QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); @@ -723,13 +774,13 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); -// TODO: Implement shortcut creation for macOS -#if !defined(__APPLE__) + // TODO: Implement shortcut creation for macOS + #if !defined(__APPLE__) QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); QAction* create_applications_menu_shortcut = - shortcut_menu->addAction(tr("Add to Applications Menu")); -#endif + shortcut_menu->addAction(tr("Add to Applications Menu")); + #endif context_menu.addSeparator(); QAction* properties = context_menu.addAction(tr("Properties")); @@ -800,22 +851,22 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); -// TODO: Implement shortcut creation for macOS -#if !defined(__APPLE__) + // TODO: Implement shortcut creation for macOS + #if !defined(__APPLE__) connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); }); connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); }); -#endif + #endif connect(properties, &QAction::triggered, [this, path]() { emit OpenPerGameGeneralRequested(path); }); }; void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { UISettings::GameDir& game_dir = - UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()]; + UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()]; QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); @@ -955,6 +1006,7 @@ void GameList::RetranslateUI() { item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); + item_model->setHeaderData(COLUMN_ONLINE, Qt::Horizontal, tr("Online")); } void GameListSearchField::changeEvent(QEvent* event) { @@ -992,7 +1044,7 @@ void GameList::PopulateAsync(QVector& game_dirs) { search_field->clear(); current_worker = std::make_unique(vfs, provider, game_dirs, compatibility_list, - play_time_manager, system); + play_time_manager, system, main_window->GetMultiplayerState()->GetSession()); // Get events from the worker as data becomes available connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, @@ -1022,46 +1074,46 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; -void GameList::RefreshGameDirectory() { - if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { - LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); - PopulateAsync(UISettings::values.game_dirs); - } -} - -void GameList::ToggleFavorite(u64 program_id) { - if (!UISettings::values.favorited_ids.contains(program_id)) { - tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), - !search_field->filterText().isEmpty()); - UISettings::values.favorited_ids.append(program_id); - AddFavorite(program_id); - item_model->sort(tree_view->header()->sortIndicatorSection(), - tree_view->header()->sortIndicatorOrder()); - } else { - UISettings::values.favorited_ids.removeOne(program_id); - RemoveFavorite(program_id); - if (UISettings::values.favorited_ids.size() == 0) { - tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); + void GameList::RefreshGameDirectory() { + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + PopulateAsync(UISettings::values.game_dirs); } } - // Update grid view if it's currently active - if (list_view->isVisible()) { - PopulateGridView(); + void GameList::ToggleFavorite(u64 program_id) { + if (!UISettings::values.favorited_ids.contains(program_id)) { + tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), + !search_field->filterText().isEmpty()); + UISettings::values.favorited_ids.append(program_id); + AddFavorite(program_id); + item_model->sort(tree_view->header()->sortIndicatorSection(), + tree_view->header()->sortIndicatorOrder()); + } else { + UISettings::values.favorited_ids.removeOne(program_id); + RemoveFavorite(program_id); + if (UISettings::values.favorited_ids.size() == 0) { + tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); + } + } + + // Update grid view if it's currently active + if (list_view->isVisible()) { + PopulateGridView(); + } + + SaveConfig(); } - SaveConfig(); -} + void GameList::AddFavorite(u64 program_id) { + auto* favorites_row = item_model->item(0); -void GameList::AddFavorite(u64 program_id) { - auto* favorites_row = item_model->item(0); - - for (int i = 1; i < item_model->rowCount() - 1; i++) { - const auto* folder = item_model->item(i); - for (int j = 0; j < folder->rowCount(); j++) { - if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() == - program_id) { - QList list; + for (int i = 1; i < item_model->rowCount() - 1; i++) { + const auto* folder = item_model->item(i); + for (int j = 0; j < folder->rowCount(); j++) { + if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() == + program_id) { + QList list; for (int k = 0; k < COLUMN_COUNT; k++) { list.append(folder->child(j, k)->clone()); } @@ -1071,149 +1123,149 @@ void GameList::AddFavorite(u64 program_id) { favorites_row->appendRow(list); return; + } } } } -} -void GameList::RemoveFavorite(u64 program_id) { - auto* favorites_row = item_model->item(0); + void GameList::RemoveFavorite(u64 program_id) { + auto* favorites_row = item_model->item(0); - for (int i = 0; i < favorites_row->rowCount(); i++) { - const auto* game = favorites_row->child(i); - if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { - favorites_row->removeRow(i); - return; + for (int i = 0; i < favorites_row->rowCount(); i++) { + const auto* game = favorites_row->child(i); + if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { + favorites_row->removeRow(i); + return; + } } } -} -GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { - connect(parent, &GMainWindow::UpdateThemedIcons, this, - &GameListPlaceholder::onUpdateThemedIcons); + GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { + connect(parent, &GMainWindow::UpdateThemedIcons, this, + &GameListPlaceholder::onUpdateThemedIcons); - layout = new QVBoxLayout; - image = new QLabel; - text = new QLabel; - layout->setAlignment(Qt::AlignCenter); - image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); + layout = new QVBoxLayout; + image = new QLabel; + text = new QLabel; + layout->setAlignment(Qt::AlignCenter); + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); - RetranslateUI(); - QFont font = text->font(); - font.setPointSize(20); - text->setFont(font); - text->setAlignment(Qt::AlignHCenter); - image->setAlignment(Qt::AlignHCenter); - - layout->addWidget(image); - layout->addWidget(text); - setLayout(layout); -} - -GameListPlaceholder::~GameListPlaceholder() = default; - -void GameListPlaceholder::onUpdateThemedIcons() { - image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); -} - -void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { - emit GameListPlaceholder::AddDirectory(); -} - -void GameListPlaceholder::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) { RetranslateUI(); + QFont font = text->font(); + font.setPointSize(20); + text->setFont(font); + text->setAlignment(Qt::AlignHCenter); + image->setAlignment(Qt::AlignHCenter); + + layout->addWidget(image); + layout->addWidget(text); + setLayout(layout); } - QWidget::changeEvent(event); -} + GameListPlaceholder::~GameListPlaceholder() = default; -void GameListPlaceholder::RetranslateUI() { - text->setText(tr("Double-click to add a new folder to the game list")); -} - -void GameList::SetViewMode(bool grid_view) { - if (grid_view) { - // Create a flat model for grid view showing only games - PopulateGridView(); - tree_view->setVisible(false); - list_view->setVisible(true); - // Only set current index if the model has items - if (list_view->model() && list_view->model()->rowCount() > 0) { - list_view->setCurrentIndex(list_view->model()->index(0, 0)); - } - } else { - // Restore the hierarchical model for tree view - list_view->setVisible(false); - tree_view->setVisible(true); - // Only set current index if the model has items - if (item_model && item_model->rowCount() > 0) { - tree_view->setCurrentIndex(item_model->index(0, 0)); - } - } -} - -void GameList::PopulateGridView() { - // Store the current hierarchical model - QStandardItemModel* hierarchical_model = item_model; - - // Delete the previous flat model if it exists to prevent memory leaks - if (QAbstractItemModel* old_model = list_view->model()) { - if (old_model != item_model) { - old_model->deleteLater(); - } + void GameListPlaceholder::onUpdateThemedIcons() { + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); } - // Create a new flat model for grid view - QStandardItemModel* flat_model = new QStandardItemModel(this); + void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { + emit GameListPlaceholder::AddDirectory(); + } - // Collect all games from the hierarchical model - for (int i = 0; i < hierarchical_model->rowCount(); ++i) { - QStandardItem* folder = hierarchical_model->item(i, 0); - if (!folder) continue; - - // Skip non-game folders in grid view, but include favorites - const auto folder_type = folder->data(GameListItem::TypeRole).value(); - if (folder_type == GameListItemType::AddDir) { - continue; + void GameListPlaceholder::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); } - // Add games from this folder to the flat model - for (int j = 0; j < folder->rowCount(); ++j) { - QStandardItem* game_item = folder->child(j, 0); - if (!game_item) continue; + QWidget::changeEvent(event); + } - const auto game_type = game_item->data(GameListItem::TypeRole).value(); - if (game_type == GameListItemType::Game) { - // Clone the game item for the flat model - QStandardItem* cloned_item = game_item->clone(); + void GameListPlaceholder::RetranslateUI() { + text->setText(tr("Double-click to add a new folder to the game list")); + } - // Set display text to just the game title for grid view - QString game_title = game_item->data(GameListItemPath::TitleRole).toString(); - if (game_title.isEmpty()) { - // Fallback to filename if no title - std::string filename; - Common::SplitPath(game_item->data(GameListItemPath::FullPathRole).toString().toStdString(), - nullptr, &filename, nullptr); - game_title = QString::fromStdString(filename); - } - cloned_item->setText(game_title); - - flat_model->appendRow(cloned_item); + void GameList::SetViewMode(bool grid_view) { + if (grid_view) { + // Create a flat model for grid view showing only games + PopulateGridView(); + tree_view->setVisible(false); + list_view->setVisible(true); + // Only set current index if the model has items + if (list_view->model() && list_view->model()->rowCount() > 0) { + list_view->setCurrentIndex(list_view->model()->index(0, 0)); + } + } else { + // Restore the hierarchical model for tree view + list_view->setVisible(false); + tree_view->setVisible(true); + // Only set current index if the model has items + if (item_model && item_model->rowCount() > 0) { + tree_view->setCurrentIndex(item_model->index(0, 0)); } } } - // Set the flat model for the list view - list_view->setModel(flat_model); + void GameList::PopulateGridView() { + // Store the current hierarchical model + QStandardItemModel* hierarchical_model = item_model; - // Update grid size based on icon size - const u32 icon_size = UISettings::values.game_icon_size.GetValue(); - list_view->setGridSize(QSize(icon_size + 60, icon_size + 80)); // More padding for round icons and text -} + // Delete the previous flat model if it exists to prevent memory leaks + if (QAbstractItemModel* old_model = list_view->model()) { + if (old_model != item_model) { + old_model->deleteLater(); + } + } -void GameList::ToggleViewMode() { - bool current_grid_view = UISettings::values.game_list_grid_view.GetValue(); - UISettings::values.game_list_grid_view.SetValue(!current_grid_view); - SetViewMode(!current_grid_view); -} + // Create a new flat model for grid view + QStandardItemModel* flat_model = new QStandardItemModel(this); + + // Collect all games from the hierarchical model + for (int i = 0; i < hierarchical_model->rowCount(); ++i) { + QStandardItem* folder = hierarchical_model->item(i, 0); + if (!folder) continue; + + // Skip non-game folders in grid view, but include favorites + const auto folder_type = folder->data(GameListItem::TypeRole).value(); + if (folder_type == GameListItemType::AddDir) { + continue; + } + + // Add games from this folder to the flat model + for (int j = 0; j < folder->rowCount(); ++j) { + QStandardItem* game_item = folder->child(j, 0); + if (!game_item) continue; + + const auto game_type = game_item->data(GameListItem::TypeRole).value(); + if (game_type == GameListItemType::Game) { + // Clone the game item for the flat model + QStandardItem* cloned_item = game_item->clone(); + + // Set display text to just the game title for grid view + QString game_title = game_item->data(GameListItemPath::TitleRole).toString(); + if (game_title.isEmpty()) { + // Fallback to filename if no title + std::string filename; + Common::SplitPath(game_item->data(GameListItemPath::FullPathRole).toString().toStdString(), + nullptr, &filename, nullptr); + game_title = QString::fromStdString(filename); + } + cloned_item->setText(game_title); + + flat_model->appendRow(cloned_item); + } + } + } + + // Set the flat model for the list view + list_view->setModel(flat_model); + + // Update grid size based on icon size + const u32 icon_size = UISettings::values.game_icon_size.GetValue(); + list_view->setGridSize(QSize(icon_size + 60, icon_size + 80)); // More padding for round icons and text + } + + void GameList::ToggleViewMode() { + bool current_grid_view = UISettings::values.game_list_grid_view.GetValue(); + UISettings::values.game_list_grid_view.SetValue(!current_grid_view); + SetViewMode(!current_grid_view); + } From 83cc2c9dd8cd863c6435dd176bbdc3804d1609cc Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:14:58 +0000 Subject: [PATCH 3/7] Edit game_list_p.h --- src/citron/game_list_p.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/citron/game_list_p.h b/src/citron/game_list_p.h index eb9609471..d87ecb858 100644 --- a/src/citron/game_list_p.h +++ b/src/citron/game_list_p.h @@ -278,6 +278,26 @@ public: } }; +class GameListItemOnline : public GameListItem { +public: + static constexpr int OnlineRole = SortRole; + + GameListItemOnline() { + + setData(QStringLiteral("N/A"), Qt::DisplayRole); + setData(QStringLiteral("N/A"), OnlineRole); + } + + explicit GameListItemOnline(const QString& online_status) { + setData(online_status, Qt::DisplayRole); + setData(online_status, OnlineRole); + } + + bool operator<(const QStandardItem& other) const override { + return data(OnlineRole).toString() < other.data(OnlineRole).toString(); + } +}; + class GameListDir : public GameListItem { public: static constexpr int GameDirRole = Qt::UserRole + 2; From df0247d42de82830f22a7d12dca52b6d495ae543 Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:16:09 +0000 Subject: [PATCH 4/7] Edit game_list_worker.h --- src/citron/game_list_worker.h | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/citron/game_list_worker.h b/src/citron/game_list_worker.h index 04d628dcf..723bbeb51 100644 --- a/src/citron/game_list_worker.h +++ b/src/citron/game_list_worker.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -7,6 +8,8 @@ #include #include #include +#include // Required for the online_stats map +#include // Required for std::pair #include #include @@ -16,17 +19,21 @@ #include "common/thread.h" #include "citron/compatibility_list.h" #include "citron/play_time_manager.h" +#include "citron/multiplayer/state.h" +#include "network/announce_multiplayer_session.h" namespace Core { -class System; + class System; } class GameList; +class GameListDir; // Forward declare GameListDir class QStandardItem; namespace FileSys { -class NCA; -class VfsFilesystem; + class NCA; + class VfsFilesystem; + class ManualContentProvider; } // namespace FileSys /** @@ -37,12 +44,18 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: + enum class ScanTarget { + FillManualContentProvider, + PopulateGameList, + }; + explicit GameListWorker(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_); + Core::System& system_, + std::shared_ptr session_); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -66,15 +79,12 @@ private: void RecordEvent(F&& func); private: - void AddTitlesToGameList(GameListDir* parent_dir); - - enum class ScanTarget { - FillManualContentProvider, - PopulateGameList, - }; + void AddTitlesToGameList(GameListDir* parent_dir, + const std::map>& online_stats); void ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, - GameListDir* parent_dir); + GameListDir* parent_dir, + const std::map>& online_stats); std::shared_ptr vfs; FileSys::ManualContentProvider* provider; @@ -91,4 +101,5 @@ private: Common::Event processing_completed; Core::System& system; + std::shared_ptr session; }; From 18c37c0ebf7837bf86742d24fbe9eb1c8affc866 Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:16:45 +0000 Subject: [PATCH 5/7] Edit game_list_worker.cpp --- src/citron/game_list_worker.cpp | 65 ++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/citron/game_list_worker.cpp b/src/citron/game_list_worker.cpp index ca2d5c79f..301a5e03a 100644 --- a/src/citron/game_list_worker.cpp +++ b/src/citron/game_list_worker.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -191,12 +192,12 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, return out; } -QList MakeGameListEntry(const std::string& path, const std::string& name, - const std::size_t size, const std::vector& icon, - Loader::AppLoader& loader, u64 program_id, - const CompatibilityList& compatibility_list, - const PlayTime::PlayTimeManager& play_time_manager, - const FileSys::PatchManager& patch) { +QList MakeGameListEntry( + const std::string& path, const std::string& name, const std::size_t size, + const std::vector& icon, Loader::AppLoader& loader, u64 program_id, + const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager, + const FileSys::PatchManager& patch, + const std::map>& online_stats) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); // The game list uses this as compatibility number for untested games @@ -208,6 +209,13 @@ QList MakeGameListEntry(const std::string& path, const std::stri const auto file_type = loader.GetFileType(); const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); + QString online_text = QStringLiteral("N/A"); + auto it_stats = online_stats.find(program_id); + if (it_stats != online_stats.end()) { + const auto& stats = it_stats->second; + online_text = QStringLiteral("Players: %1 | Servers: %2").arg(stats.first).arg(stats.second); + } + QList list{ new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), file_type_string, program_id), @@ -215,7 +223,7 @@ QList MakeGameListEntry(const std::string& path, const std::stri new GameListItem(file_type_string), new GameListItemSize(size), new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), - }; + new GameListItemOnline(online_text)}; const auto patch_versions = GetGameListCachedObject( fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { @@ -227,15 +235,16 @@ QList MakeGameListEntry(const std::string& path, const std::stri } } // Anonymous namespace -GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, +GameListWorker::GameListWorker(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_) + Core::System& system_, + std::shared_ptr session_) : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ - system_} { + compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, + system{system_}, session{session_} { // We want the game list to manage our lifetime. setAutoDelete(false); } @@ -282,7 +291,7 @@ void GameListWorker::RecordEvent(F&& func) { emit DataAvailable(); } -void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { +void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir, const std::map>& online_stats) { using namespace FileSys; const auto& cache = system.GetContentProviderUnion(); @@ -329,14 +338,14 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); + program_id, compatibility_list, play_time_manager, patch, online_stats); RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, - GameListDir* parent_dir) { - const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { + GameListDir* parent_dir, const std::map>& online_stats) { + const auto callback = [this, target, parent_dir, &online_stats](const std::filesystem::path& path) -> bool { if (stop_requested) { // Breaks the callback loop. return false; @@ -406,7 +415,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa auto entry = MakeGameListEntry( physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - id, compatibility_list, play_time_manager, patch); + id, compatibility_list, play_time_manager, patch, online_stats); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); @@ -423,7 +432,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa auto entry = MakeGameListEntry( physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); + program_id, compatibility_list, play_time_manager, patch, online_stats); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); @@ -445,6 +454,18 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } void GameListWorker::run() { + std::map> online_stats; // Game ID -> {player_count, server_count} + if (session) { + AnnounceMultiplayerRoom::RoomList room_list = session->GetRoomList(); + for (const auto& room : room_list) { + u64 game_id = room.information.preferred_game.id; + if (game_id != 0) { + online_stats[game_id].first += room.members.size(); + online_stats[game_id].second++; + } + } + } + watch_list.clear(); provider->ClearAllEntries(); @@ -460,23 +481,23 @@ void GameListWorker::run() { if (game_dir.path == std::string("SDMC")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + AddTitlesToGameList(game_list_dir, online_stats); } else if (game_dir.path == std::string("UserNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + AddTitlesToGameList(game_list_dir, online_stats); } else if (game_dir.path == std::string("SysNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + AddTitlesToGameList(game_list_dir, online_stats); } else { watch_list.append(QString::fromStdString(game_dir.path)); auto* const game_list_dir = new GameListDir(game_dir); DirEntryReady(game_list_dir); ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan, - game_list_dir); + game_list_dir, online_stats); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan, - game_list_dir); + game_list_dir, online_stats); } } From 3592194bedd88f471d849952dee28cc341c27fc6 Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:17:25 +0000 Subject: [PATCH 6/7] Edit state.h --- src/citron/multiplayer/state.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/citron/multiplayer/state.h b/src/citron/multiplayer/state.h index d6149838f..82f20bde2 100644 --- a/src/citron/multiplayer/state.h +++ b/src/citron/multiplayer/state.h @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -15,7 +16,7 @@ class DirectConnectWindow; class ClickableLabel; namespace Core { -class System; + class System; } class MultiplayerState : public QWidget { @@ -33,6 +34,13 @@ public: QAction* show_room, Core::System& system_); ~MultiplayerState(); + /** + * This is the new function to safely access the multiplayer session. + */ + std::shared_ptr GetSession() { + return announce_multiplayer_session; + } + /** * Close all open multiplayer related dialogs */ From 9b1184e2db620d5b563e63745400f31c4fe1ef1b Mon Sep 17 00:00:00 2001 From: collecting Date: Sat, 4 Oct 2025 04:17:56 +0000 Subject: [PATCH 7/7] Edit main.h --- src/citron/main.h | 79 ++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/src/citron/main.h b/src/citron/main.h index 30f1f069e..4db6001a7 100644 --- a/src/citron/main.h +++ b/src/citron/main.h @@ -72,59 +72,59 @@ enum class StartGameType { }; namespace Core { -enum class SystemResultStatus : u32; -class System; + enum class SystemResultStatus : u32; + class System; } // namespace Core namespace Core::Frontend { -struct CabinetParameters; -struct ControllerParameters; -struct InlineAppearParameters; -struct InlineTextParameters; -struct KeyboardInitializeParameters; -struct ProfileSelectParameters; + struct CabinetParameters; + struct ControllerParameters; + struct InlineAppearParameters; + struct InlineTextParameters; + struct KeyboardInitializeParameters; + struct ProfileSelectParameters; } // namespace Core::Frontend namespace DiscordRPC { -class DiscordInterface; + class DiscordInterface; } namespace PlayTime { -class PlayTimeManager; + class PlayTimeManager; } namespace FileSys { -class ContentProvider; -class ManualContentProvider; -class VfsFilesystem; + class ContentProvider; + class ManualContentProvider; + class VfsFilesystem; } // namespace FileSys namespace InputCommon { -class InputSubsystem; + class InputSubsystem; } namespace Service::AM { -struct FrontendAppletParameters; -enum class AppletId : u32; + struct FrontendAppletParameters; + enum class AppletId : u32; } // namespace Service::AM namespace Service::AM::Frontend { -enum class SwkbdResult : u32; -enum class SwkbdTextCheckResult : u32; -enum class SwkbdReplyType : u32; -enum class WebExitReason : u32; + enum class SwkbdResult : u32; + enum class SwkbdTextCheckResult : u32; + enum class SwkbdReplyType : u32; + enum class WebExitReason : u32; } // namespace Service::AM::Frontend namespace Service::NFC { -class NfcDevice; + class NfcDevice; } // namespace Service::NFC namespace Service::NFP { -enum class CabinetMode : u8; + enum class CabinetMode : u8; } // namespace Service::NFP namespace Ui { -class MainWindow; + class MainWindow; } enum class EmulatedDirectoryTarget { @@ -133,7 +133,7 @@ enum class EmulatedDirectoryTarget { }; namespace VkDeviceInfo { -class Record; + class Record; } class VolumeButton : public QPushButton { @@ -183,6 +183,13 @@ public: bool DropAction(QDropEvent* event); void AcceptDropEvent(QDropEvent* event); + /** + * This is the new function to provide access to the MultiplayerState. + */ + MultiplayerState* GetMultiplayerState() { + return multiplayer_state; + } + signals: /** @@ -326,16 +333,16 @@ private: std::string CreateTASFramesString( std::array frames) const; -#ifdef __unix__ - void SetupSigInterrupts(); - static void HandleSigInterrupt(int); - void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); -#endif + #ifdef __unix__ + void SetupSigInterrupts(); + static void HandleSigInterrupt(int); + void OnSigInterruptNotifierActivated(); + void SetGamemodeEnabled(bool state); + #endif - Service::AM::FrontendAppletParameters ApplicationAppletParameters(); - Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, - Service::AM::AppletId applet_id); + Service::AM::FrontendAppletParameters ApplicationAppletParameters(); + Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, + Service::AM::AppletId applet_id); private slots: void OnStartGame(); @@ -495,7 +502,7 @@ private: */ bool question(QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons = - QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); std::unique_ptr ui; @@ -598,10 +605,10 @@ private: // True if TAS recording dialog is visible bool is_tas_recording_dialog_active{}; -#ifdef __unix__ + #ifdef __unix__ QSocketNotifier* sig_interrupt_notifier; static std::array sig_interrupt_fds; -#endif + #endif protected: void dropEvent(QDropEvent* event) override;