[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>
This commit is contained in:
crueter
2026-06-19 22:55:29 +02:00
parent 7c0e993b5b
commit 102d254530
12 changed files with 181 additions and 277 deletions
+13 -68
View File
@@ -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"));