Qt: Post Game-Installation Assistant

This commit is contained in:
Eladash 2023-12-01 22:13:02 +02:00 committed by Elad Ashkenazi
parent cb4a688e02
commit a6839e823e
5 changed files with 239 additions and 99 deletions

View file

@ -3717,8 +3717,9 @@ game_boot_result Emulator::AddGameToYml(const std::string& path)
bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const bool Emulator::IsPathInsideDir(std::string_view path, std::string_view dir) const
{ {
const std::string dir_path = GetCallbacks().resolve_path(dir); const std::string dir_path = GetCallbacks().resolve_path(dir);
const std::string resolved_path = GetCallbacks().resolve_path(path);
return !dir_path.empty() && (GetCallbacks().resolve_path(path) + '/').starts_with((dir_path.back() == '/') ? dir_path : (dir_path + '/')); return !dir_path.empty() && !resolved_path.empty() && (resolved_path + '/').starts_with((dir_path.back() == '/') ? dir_path : (dir_path + '/'));
} }
game_boot_result Emulator::VerifyPathCasing( game_boot_result Emulator::VerifyPathCasing(

View file

@ -813,6 +813,11 @@ void game_list_frame::OnRefreshFinished()
m_progress_dialog->deleteLater(); m_progress_dialog->deleteLater();
m_progress_dialog = nullptr; m_progress_dialog = nullptr;
} }
// Emit signal and remove slots
Q_EMIT Refreshed();
m_refresh_funcs_manage_type.reset();
m_refresh_funcs_manage_type.emplace();
} }
void game_list_frame::OnCompatFinished() void game_list_frame::OnCompatFinished()
@ -1923,11 +1928,11 @@ void game_list_frame::RemoveHDD1Cache(const std::string& base_dir, const std::st
game_list_log.fatal("Only %d/%d HDD1 cache directories could be removed in %s (%s)", dirs_removed, dirs_total, base_dir, title_id); game_list_log.fatal("Only %d/%d HDD1 cache directories could be removed in %s (%s)", dirs_removed, dirs_total, base_dir, title_id);
} }
void game_list_frame::BatchCreateCPUCaches() void game_list_frame::BatchCreateCPUCaches(const QList<game_info>& game_data)
{ {
const std::string vsh_path = g_cfg_vfs.get_dev_flash() + "vsh/module/"; const std::string vsh_path = g_cfg_vfs.get_dev_flash() + "vsh/module/";
const bool vsh_exists = fs::is_file(vsh_path + "vsh.self"); const bool vsh_exists = game_data.isEmpty() && fs::is_file(vsh_path + "vsh.self");
const u32 total = m_game_data.size() + (vsh_exists ? 1 : 0); const u32 total = !game_data.isEmpty() ? game_data.size() : (m_game_data.size() + (vsh_exists ? 1 : 0));
if (total == 0) if (total == 0)
{ {
@ -1975,7 +1980,7 @@ void game_list_frame::BatchCreateCPUCaches()
} }
} }
for (const auto& game : m_game_data) for (const auto& game : (game_data.isEmpty() ? m_game_data : game_data))
{ {
if (pdlg->wasCanceled() || g_system_progress_canceled) if (pdlg->wasCanceled() || g_system_progress_canceled)
{ {

View file

@ -6,6 +6,7 @@
#include "shortcut_utils.h" #include "shortcut_utils.h"
#include "Utilities/lockless.h" #include "Utilities/lockless.h"
#include "Utilities/mutex.h" #include "Utilities/mutex.h"
#include "util/auto_typemap.hpp"
#include "Emu/config_mode.h" #include "Emu/config_mode.h"
#include <QMainWindow> #include <QMainWindow>
@ -17,6 +18,7 @@
#include <QTimer> #include <QTimer>
#include <memory> #include <memory>
#include <optional>
#include <set> #include <set>
class game_list_table; class game_list_table;
@ -63,7 +65,7 @@ public:
bool IsEntryVisible(const game_info& game, bool search_fallback = false) const; bool IsEntryVisible(const game_info& game, bool search_fallback = false) const;
public Q_SLOTS: public Q_SLOTS:
void BatchCreateCPUCaches(); void BatchCreateCPUCaches(const QList<game_info>& game_data = QList<game_info>{});
void BatchRemovePPUCaches(); void BatchRemovePPUCaches();
void BatchRemoveSPUCaches(); void BatchRemoveSPUCaches();
void BatchRemoveCustomConfigurations(); void BatchRemoveCustomConfigurations();
@ -92,6 +94,31 @@ Q_SIGNALS:
void RequestIconSizeChange(const int& val); void RequestIconSizeChange(const int& val);
void NotifyEmuSettingsChange(); void NotifyEmuSettingsChange();
void FocusToSearchBar(); void FocusToSearchBar();
void Refreshed();
public:
template <typename KeyType>
struct GameIdsTable
{
// List of Game IDS an operation has been done on for the use of the slot function
std::set<QString> m_done_ids;
};
template <typename KeySlot, typename Func>
void AddRefreshedSlot(Func&& func)
{
if (!m_refresh_funcs_manage_type.has_value())
{
m_refresh_funcs_manage_type.emplace();
}
connect(this, &game_list_frame::Refreshed, this, [this, func = std::move(func)]() mutable
{
func(m_refresh_funcs_manage_type->get<GameIdsTable<KeySlot>>().m_done_ids);
}, Qt::SingleShotConnection);
}
protected: protected:
/** Override inherited method from Qt to allow signalling when close happened.*/ /** Override inherited method from Qt to allow signalling when close happened.*/
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
@ -168,6 +195,7 @@ private:
const std::array<int, 1> m_parsing_threads{0}; const std::array<int, 1> m_parsing_threads{0};
QFutureWatcher<void> m_parsing_watcher; QFutureWatcher<void> m_parsing_watcher;
QFutureWatcher<void> m_refresh_watcher; QFutureWatcher<void> m_refresh_watcher;
usz m_refresh_counter = 0;
QSet<QString> m_hidden_list; QSet<QString> m_hidden_list;
bool m_show_hidden{false}; bool m_show_hidden{false};
@ -185,4 +213,5 @@ private:
bool m_draw_compat_status_to_grid = false; bool m_draw_compat_status_to_grid = false;
bool m_show_custom_icons = true; bool m_show_custom_icons = true;
bool m_play_hover_movies = true; bool m_play_hover_movies = true;
std::optional<auto_typemap<game_list_frame>> m_refresh_funcs_manage_type;
}; };

View file

@ -39,6 +39,7 @@
#include <thread> #include <thread>
#include <charconv> #include <charconv>
#include <unordered_set>
#include <QScreen> #include <QScreen>
#include <QDirIterator> #include <QDirIterator>
@ -1046,7 +1047,8 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
if (success) if (success)
{ {
pdlg.SetValue(pdlg.maximum()); pdlg.SetValue(pdlg.maximum());
std::this_thread::sleep_for(100ms);
const u64 start_time = get_system_time();
for (usz i = 0; i < packages.size(); i++) for (usz i = 0; i < packages.size(); i++)
{ {
@ -1081,8 +1083,6 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
} }
} }
m_game_list_frame->Refresh(true);
std::map<std::string, QString> bootable_paths_installed; // -> title id std::map<std::string, QString> bootable_paths_installed; // -> title id
for (usz index = 0; index < bootable_paths.size(); index++) for (usz index = 0; index < bootable_paths.size(); index++)
@ -1095,80 +1095,44 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
bootable_paths_installed[bootable_paths[index]] = packages[index].title_id; bootable_paths_installed[bootable_paths[index]] = packages[index].title_id;
} }
pdlg.hide(); const bool installed_a_whole_package_without_new_software = bootable_paths_installed.empty() && !cancelled;
if (!cancelled || !bootable_paths_installed.empty()) if (!bootable_paths_installed.empty())
{ {
if (bootable_paths_installed.empty()) m_game_list_frame->AddRefreshedSlot<class KeyType>([this, paths = std::move(bootable_paths_installed)](std::set<QString>& IDs) mutable
{ {
m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); // Try to claim operaions on ID
return true; for (auto it = paths.begin(); it != paths.end();)
}
auto dlg = new QDialog(this);
dlg->setWindowTitle(tr("Success!"));
QVBoxLayout* vlayout = new QVBoxLayout(dlg);
QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)"));
#ifdef _WIN32
QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)"));
#elif defined(__APPLE__)
QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)"));
#else
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
#endif
QLabel* label = new QLabel(tr("Successfully installed software from package(s)!\nWould you like to install shortcuts to the installed software? (%1 new software detected)\n\n").arg(bootable_paths_installed.size()), dlg);
vlayout->addWidget(label);
vlayout->addStretch(10);
vlayout->addWidget(desk_check);
vlayout->addStretch(3);
vlayout->addWidget(quick_check);
vlayout->addStretch(3);
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
vlayout->addWidget(btn_box);
dlg->setLayout(vlayout);
bool create_desktop_shortcuts = false;
bool create_app_shortcut = false;
connect(btn_box, &QDialogButtonBox::accepted, this, [&]()
{
create_desktop_shortcuts = desk_check->isChecked();
create_app_shortcut = quick_check->isChecked();
dlg->accept();
});
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->exec();
std::set<gui::utils::shortcut_location> locations;
#ifdef _WIN32
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
#endif
if (create_desktop_shortcuts)
{
locations.insert(gui::utils::shortcut_location::desktop);
}
if (create_app_shortcut)
{
locations.insert(gui::utils::shortcut_location::applications);
}
for (const auto& [boot_path, title_id] : bootable_paths_installed)
{
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
{ {
if (gameinfo && gameinfo->info.bootable && gameinfo->info.serial == sstr(title_id) && boot_path.starts_with(gameinfo->info.path)) if (IDs.count(it->second))
{ {
m_game_list_frame->CreateShortcuts(gameinfo, locations); it = paths.erase(it);
break; }
else
{
IDs.emplace(it->second);
it++;
} }
} }
}
ShowOptionalGamePreparations(tr("Success!"), tr("Successfully installed software from package(s)!"), std::move(paths));
});
}
m_game_list_frame->Refresh(true);
std::this_thread::sleep_for(std::chrono::microseconds(100'000 - std::min<usz>(100'000, get_system_time() - start_time)));
pdlg.hide();
if (installed_a_whole_package_without_new_software)
{
m_gui_settings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this);
return true;
}
if (!cancelled)
{
return true;
} }
} }
else else
@ -2282,6 +2246,104 @@ void main_window::ShowTitleBars(bool show) const
m_log_frame->SetTitleBarVisible(show); m_log_frame->SetTitleBarVisible(show);
} }
void main_window::ShowOptionalGamePreparations(const QString& title, const QString& message, std::map<std::string, QString> bootable_paths)
{
if (bootable_paths.empty())
{
m_gui_settings->ShowInfoBox(title, message, gui::ib_pkg_success, this);
return;
}
QDialog* dlg = new QDialog(this);
dlg->setObjectName("game_prepare_window");
dlg->setWindowTitle(title);
QVBoxLayout* vlayout = new QVBoxLayout(dlg);
QCheckBox* desk_check = new QCheckBox(tr("Add desktop shortcut(s)"));
#ifdef _WIN32
QCheckBox* quick_check = new QCheckBox(tr("Add Start menu shortcut(s)"));
#elif defined(__APPLE__)
QCheckBox* quick_check = new QCheckBox(tr("Add dock shortcut(s)"));
#else
QCheckBox* quick_check = new QCheckBox(tr("Add launcher shortcut(s)"));
#endif
QCheckBox* precompile_check = new QCheckBox(tr("Precompile caches"));
QLabel* label = new QLabel(tr("%1\nWould you like to install shortcuts to the installed software and precompile caches? (%2 new software detected)\n\n").arg(message).arg(bootable_paths.size()), dlg);
vlayout->addWidget(label);
vlayout->addStretch(10);
vlayout->addWidget(desk_check);
vlayout->addStretch(3);
vlayout->addWidget(quick_check);
vlayout->addStretch(3);
vlayout->addWidget(precompile_check);
vlayout->addStretch(3);
precompile_check->setToolTip(tr("Spend time building data needed for game boot now instead of at launch."));
QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok);
vlayout->addWidget(btn_box);
dlg->setLayout(vlayout);
connect(btn_box, &QDialogButtonBox::accepted, this, [=, paths = std::move(bootable_paths)]()
{
const bool create_desktop_shortcuts = desk_check->isChecked();
const bool create_app_shortcut = quick_check->isChecked();
const bool create_caches = precompile_check->isChecked();
dlg->hide();
dlg->accept();
std::set<gui::utils::shortcut_location> locations;
#ifdef _WIN32
locations.insert(gui::utils::shortcut_location::rpcs3_shortcuts);
#endif
if (create_desktop_shortcuts)
{
locations.insert(gui::utils::shortcut_location::desktop);
}
if (create_app_shortcut)
{
locations.insert(gui::utils::shortcut_location::applications);
}
QList<game_info> game_data;
for (const auto& [boot_path, title_id] : paths)
{
for (const game_info& gameinfo : m_game_list_frame->GetGameInfo())
{
if (gameinfo && gameinfo->info.serial == sstr(title_id))
{
if (Emu.IsPathInsideDir(boot_path, gameinfo->info.path))
{
m_game_list_frame->CreateShortcuts(gameinfo, locations);
if (create_caches)
{
game_data.push_back(gameinfo);
}
}
break;
}
}
}
if (!game_data.isEmpty())
{
m_game_list_frame->BatchCreateCPUCaches(game_data);
}
});
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->open();
}
void main_window::CreateActions() void main_window::CreateActions()
{ {
ui->exitAct->setShortcuts(QKeySequence::Quit); ui->exitAct->setShortcuts(QKeySequence::Quit);
@ -2344,17 +2406,7 @@ void main_window::CreateConnects()
// Only select one folder for now // Only select one folder for now
paths << QFileDialog::getExistingDirectory(this, tr("Select a folder containing one or more games"), qstr(fs::get_config_dir()), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); paths << QFileDialog::getExistingDirectory(this, tr("Select a folder containing one or more games"), qstr(fs::get_config_dir()), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
AddGamesFromDirs(paths);
if (!paths.isEmpty())
{
Emu.GracefulShutdown(false);
for (const QString& path : paths)
{
AddGamesFromDir(path);
}
m_game_list_frame->Refresh(true);
}
}); });
connect(ui->bootRecentMenu, &QMenu::aboutToShow, this, [this]() connect(ui->bootRecentMenu, &QMenu::aboutToShow, this, [this]()
@ -2525,7 +2577,7 @@ void main_window::CreateConnects()
}); });
connect(ui->exitAct, &QAction::triggered, this, &QWidget::close); connect(ui->exitAct, &QAction::triggered, this, &QWidget::close);
connect(ui->batchCreateCPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchCreateCPUCaches); connect(ui->batchCreateCPUCachesAct, &QAction::triggered, m_game_list_frame, [list = m_game_list_frame]() { list->BatchCreateCPUCaches(); });
connect(ui->batchRemovePPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemovePPUCaches); connect(ui->batchRemovePPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemovePPUCaches);
connect(ui->batchRemoveSPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemoveSPUCaches); connect(ui->batchRemoveSPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemoveSPUCaches);
connect(ui->batchRemoveShaderCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemoveShaderCaches); connect(ui->batchRemoveShaderCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemoveShaderCaches);
@ -3446,16 +3498,73 @@ void main_window::closeEvent(QCloseEvent* closeEvent)
/** /**
Add valid disc games to gamelist (games.yml) Add valid disc games to gamelist (games.yml)
@param path = dir path to scan for game @param paths = dir paths to scan for game
*/ */
void main_window::AddGamesFromDir(const QString& path) void main_window::AddGamesFromDirs(const QStringList& paths)
{ {
if (!QFileInfo(path).isDir()) if (paths.isEmpty())
{ {
return; return;
} }
Emu.AddGamesFromDir(sstr(path)); // Obtain list of previously existing entries under the specificied parent paths for comparison
std::unordered_set<std::string_view> existing;
for (const game_info& game : m_game_list_frame->GetGameInfo())
{
if (game)
{
for (const auto& dir_path : paths)
{
if (dir_path.startsWith(game->info.path.c_str()) && fs::exists(game->info.path))
{
existing.insert(game->info.path);
break;
}
}
}
}
for (const QString& path : paths)
{
Emu.AddGamesFromDir(sstr(path));
}
m_game_list_frame->AddRefreshedSlot<class KeyType>([this, paths = std::move(paths), existing = std::move(existing)](std::set<QString>& IDs)
{
// Execute followup operations only for newly added entries under the specified paths
std::map<std::string, QString> paths_added; // -> title id
for (const game_info& game : m_game_list_frame->GetGameInfo())
{
if (game && !existing.contains(game->info.path))
{
for (const auto& dir_path : paths)
{
if (Emu.IsPathInsideDir(game->info.path, sstr(dir_path)))
{
// Try to claim operaion on ID
const QString title_id = qstr(game->info.serial);
if (!IDs.count(title_id))
{
IDs.emplace(title_id);
paths_added.emplace(game->info.path, title_id);
}
break;
}
}
}
}
if (!paths_added.empty())
{
ShowOptionalGamePreparations(tr("Success!"), tr("Successfully added software to game list from path(s)!"), std::move(paths_added));
}
});
m_game_list_frame->Refresh(true);
} }
/** /**
@ -3632,12 +3741,7 @@ void main_window::dropEvent(QDropEvent* event)
} }
case drop_type::drop_dir: // import valid games to gamelist (games.yaml) case drop_type::drop_dir: // import valid games to gamelist (games.yaml)
{ {
for (const auto& path : drop_paths) AddGamesFromDirs(drop_paths);
{
AddGamesFromDir(path);
}
m_game_list_frame->Refresh(true);
break; break;
} }
case drop_type::drop_game: // import valid games to gamelist (games.yaml) case drop_type::drop_game: // import valid games to gamelist (games.yaml)

View file

@ -150,6 +150,7 @@ private:
void CreateDockWindows(); void CreateDockWindows();
void EnableMenus(bool enabled) const; void EnableMenus(bool enabled) const;
void ShowTitleBars(bool show) const; void ShowTitleBars(bool show) const;
void ShowOptionalGamePreparations(const QString& title, const QString& message, std::map<std::string, QString> game_path);
static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename); static bool InstallFileInExData(const std::string& extension, const QString& path, const std::string& filename);
@ -165,7 +166,7 @@ private:
u64 m_drop_file_timestamp = umax; u64 m_drop_file_timestamp = umax;
drop_type m_drop_file_cached_drop_type = drop_type::drop_error; drop_type m_drop_file_cached_drop_type = drop_type::drop_error;
drop_type IsValidFile(const QMimeData& md, QStringList* drop_paths = nullptr); drop_type IsValidFile(const QMimeData& md, QStringList* drop_paths = nullptr);
static void AddGamesFromDir(const QString& path); void AddGamesFromDirs(const QStringList& paths);
QAction* CreateRecentAction(const q_string_pair& entry, const uint& sc_idx); QAction* CreateRecentAction(const q_string_pair& entry, const uint& sc_idx);
void BootRecentAction(const QAction* act); void BootRecentAction(const QAction* act);