diff options
| author | crueter <crueter@eden-emu.dev> | 2026-06-19 22:55:29 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2026-06-19 22:55:29 +0200 |
| commit | 102d2545303daa926f39e5cf6518c6ce99c7918d (patch) | |
| tree | ab5653bc5989530929f49c10e20024ea16a313a0 /src/qt_common | |
| parent | 7c0e993b5b905224fd4d290485deaee30c685f0d (diff) | |
[desktop] Clean up game list code, fix external watcher crash, and fix macOS flickering (#4106)
- Remove unnecessary icon update code (the UI reloads this stuff anyways); test on Windows please
- Cleaned up a bunch of duplicated/unused code within the game list
- Fix the game list constantly reloading on macOS
* When you reconstruct the entire directory list on the watcher the directoryChanged signal fires on macOS--seems like a behavioral change that occurred somewhere in the 6.8 release cycle--and it would enter an infinite loop very quickly
* To fix this, only the differences between the current and old watch list are accounted for on both ends.
* Since this bug is now fixed, macOS uses Qt 6.11.1 now. Should theoretically improve our situation.
- Fix the external content watcher crashing; the worker would attempt to read files that didn't exist without any bounds since its cache was still pointing to that file.
This supersedes and replaces #4099.
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4106
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'src/qt_common')
| -rw-r--r-- | src/qt_common/game_list/game_list_p.h | 52 | ||||
| -rw-r--r-- | src/qt_common/game_list/model.cpp | 81 | ||||
| -rw-r--r-- | src/qt_common/game_list/model.h | 9 | ||||
| -rw-r--r-- | src/qt_common/game_list/worker.cpp | 81 |
4 files changed, 73 insertions, 150 deletions
diff --git a/src/qt_common/game_list/game_list_p.h b/src/qt_common/game_list/game_list_p.h index 3994887755..a5297d0db3 100644 --- a/src/qt_common/game_list/game_list_p.h +++ b/src/qt_common/game_list/game_list_p.h @@ -48,6 +48,14 @@ static QPixmap GetDefaultIcon(u32 size) { return icon; } +static QPixmap ThemeIcon(const char* name) { + const int size = UISettings::values.folder_icon_size.GetValue(); + + return QIcon::fromTheme(QLatin1String(name)) + .pixmap(size, size) + .scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + class GameListItem : public QStandardItem { public: @@ -296,45 +304,33 @@ public: UISettings::GameDir* game_dir = &directory; setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); + const char* icon_name = nullptr; + switch (dir_type) { case GameListItemType::SdmcDir: - setData( - QIcon::fromTheme(QStringLiteral("sd_card")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "sd_card"; setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole); break; case GameListItemType::UserNandDir: - setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "chip"; setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole); break; case GameListItemType::SysNandDir: - setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "chip"; setData(QObject::tr("System Titles"), Qt::DisplayRole); break; case GameListItemType::CustomDir: { const QString path = QString::fromStdString(game_dir->path); - const QString icon_name = - QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder"); - setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( - icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = QFileInfo::exists(path) ? "folder" : "bad_folder"; setData(path, Qt::DisplayRole); break; } default: break; } + + if (icon_name != nullptr) + setData(ThemeIcon(icon_name), Qt::DecorationRole); } int type() const override { @@ -357,12 +353,7 @@ public: explicit GameListAddDir() { setData(type(), TypeRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - setData(QIcon::fromTheme(QStringLiteral("list-add")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + setData(ThemeIcon("list-add"), Qt::DecorationRole); setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); } @@ -380,12 +371,7 @@ public: explicit GameListFavorites() { setData(type(), TypeRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - setData(QIcon::fromTheme(QStringLiteral("star")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + setData(ThemeIcon("star"), Qt::DecorationRole); setData(QObject::tr("Favorites"), Qt::DisplayRole); } diff --git a/src/qt_common/game_list/model.cpp b/src/qt_common/game_list/model.cpp index 91ec80c525..5c1042b002 100644 --- a/src/qt_common/game_list/model.cpp +++ b/src/qt_common/game_list/model.cpp @@ -19,8 +19,8 @@ #include "qt_common/util/game.h" #include "qt_common/game_list/game_list_p.h" -#include "qt_common/game_list/worker.h" #include "qt_common/game_list/model.h" +#include "qt_common/game_list/worker.h" GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_, FileSys::ManualContentProvider* provider_, @@ -36,6 +36,8 @@ GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_, connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, &GameListModel::RefreshExternalContent); + ResetExternalWatcher(); + insertColumns(0, COLUMN_COUNT); RetranslateUI(); @@ -45,6 +47,8 @@ GameListModel::GameListModel(std::shared_ptr<FileSys::VfsFilesystem> vfs_, GameListModel::~GameListModel() = default; void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { + emit PopulatingStarted(); + current_worker.reset(); removeRows(0, rowCount()); @@ -57,12 +61,6 @@ void GameListModel::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { QThreadPool::globalInstance()->start(current_worker.get()); } -void GameListModel::StopWorker() { - // ~GameListWorker sets stop_requested and blocks until run() finishes, so this returns only - // once the worker is no longer touching the content providers. - current_worker.reset(); -} - void GameListModel::WorkerEvent() { current_worker->ProcessEvents(this); } @@ -203,24 +201,25 @@ void GameListModel::LoadCompatibilityList() { } } +void GameListModel::Repopulate() { + current_worker.reset(); + QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); + PopulateAsync(UISettings::values.game_dirs); +} + void GameListModel::RefreshGameDirectory() { ResetExternalWatcher(); - if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); - StopWorker(); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - PopulateAsync(UISettings::values.game_dirs); + Repopulate(); } } void GameListModel::RefreshExternalContent() { if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache."); - StopWorker(); QtCommon::Game::ResetMetadata(false); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - PopulateAsync(UISettings::values.game_dirs); + Repopulate(); } } @@ -235,60 +234,6 @@ void GameListModel::ResetExternalWatcher() { } } -void GameListModel::OnUpdateThemedIcons() { - for (int i = 0; i < invisibleRootItem()->rowCount(); i++) { - QStandardItem* child = invisibleRootItem()->child(i); - - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { - 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: - 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 = - 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")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - 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; - } - } -} - void GameListModel::RetranslateUI() { setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); diff --git a/src/qt_common/game_list/model.h b/src/qt_common/game_list/model.h index ce1fcc87b7..c8661e2fd1 100644 --- a/src/qt_common/game_list/model.h +++ b/src/qt_common/game_list/model.h @@ -3,11 +3,11 @@ #pragma once +#include <memory> #include <QFileSystemWatcher> #include <QStandardItemModel> #include <QStringList> #include <QVector> -#include <memory> #include "common/common_types.h" #include "frontend_common/play_time_manager.h" @@ -52,10 +52,6 @@ public: void DonePopulating(const QStringList& watch_list); void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); - // Stops and joins the running populate worker, if any. Must be called before rebuilding the - // content providers (CreateFactories), otherwise the worker keeps scanning a cache that is - // being torn down underneath it. - void StopWorker(); void WorkerEvent(); bool IsEmpty() const; @@ -68,7 +64,6 @@ public: void LoadCompatibilityList(); - void OnUpdateThemedIcons(); void RetranslateUI(); QFileSystemWatcher* GetWatcher() const; @@ -80,6 +75,7 @@ public: signals: void ShowList(bool show); void PopulatingCompleted(const QStringList& watch_list); + void PopulatingStarted(); void SaveConfig(); private: @@ -87,6 +83,7 @@ private: void AddFavorite(u64 program_id); void RemoveFavorite(u64 program_id); + void Repopulate(); bool m_flat = false; diff --git a/src/qt_common/game_list/worker.cpp b/src/qt_common/game_list/worker.cpp index f1499265c6..5cd68996c5 100644 --- a/src/qt_common/game_list/worker.cpp +++ b/src/qt_common/game_list/worker.cpp @@ -32,11 +32,11 @@ #include "qt_common/config/uisettings.h" #include "qt_common/qt_common.h" -#include "yuzu/compatibility_list.h" #include "qt_common/game_list/game_list_p.h" +#include "yuzu/compatibility_list.h" -#include "qt_common/game_list/worker.h" #include "qt_common/game_list/model.h" +#include "qt_common/game_list/worker.h" namespace { @@ -391,6 +391,25 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa std::vector<u64> program_ids; loader->ReadProgramIds(program_ids); + const auto addEntry = [this, physical_name, + parent_dir](std::unique_ptr<Loader::AppLoader>& app_loader, + const u64 id) { + std::vector<u8> icon; + [[maybe_unused]] const auto res1 = app_loader->ReadIcon(icon); + + std::string name = " "; + [[maybe_unused]] const auto res3 = app_loader->ReadTitle(name); + + const FileSys::PatchManager patch{id, system.GetFileSystemController(), + system.GetContentProvider()}; + + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *app_loader, + id, compatibility_list, play_time_manager, patch); + + RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); + }; + if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { for (const auto id : program_ids) { @@ -404,38 +423,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa continue; } - std::vector<u8> icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - - const FileSys::PatchManager patch{id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - id, compatibility_list, play_time_manager, patch); - - RecordEvent( - [=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); + addEntry(loader, id); } } else { - std::vector<u8> icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - - const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); - - RecordEvent( - [=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); + addEntry(loader, program_id); } } } else if (is_dir) { @@ -466,29 +457,33 @@ void GameListWorker::run() { break; } + GameListDir* game_list_dir; + bool scan = false; + 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); + game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); } 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); + game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); } 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); + game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); } else { const QString qpath = QString::fromStdString(game_dir.path); if (QDir(qpath).exists()) { watch_list.append(qpath); } - auto* const game_list_dir = new GameListDir(game_dir); - DirEntryReady(game_list_dir); + + game_list_dir = new GameListDir(game_dir); + scan = true; + } + + DirEntryReady(game_list_dir); + if (scan) { ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan, game_list_dir); + } else { + AddTitlesToGameList(game_list_dir); } } |
