1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2025-01-31 20:41:45 +01:00

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
{
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(

View File

@ -813,6 +813,11 @@ void game_list_frame::OnRefreshFinished()
m_progress_dialog->deleteLater();
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()
@ -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);
}
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 bool vsh_exists = fs::is_file(vsh_path + "vsh.self");
const u32 total = m_game_data.size() + (vsh_exists ? 1 : 0);
const bool vsh_exists = game_data.isEmpty() && fs::is_file(vsh_path + "vsh.self");
const u32 total = !game_data.isEmpty() ? game_data.size() : (m_game_data.size() + (vsh_exists ? 1 : 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)
{

View File

@ -6,6 +6,7 @@
#include "shortcut_utils.h"
#include "Utilities/lockless.h"
#include "Utilities/mutex.h"
#include "util/auto_typemap.hpp"
#include "Emu/config_mode.h"
#include <QMainWindow>
@ -17,6 +18,7 @@
#include <QTimer>
#include <memory>
#include <optional>
#include <set>
class game_list_table;
@ -63,7 +65,7 @@ public:
bool IsEntryVisible(const game_info& game, bool search_fallback = false) const;
public Q_SLOTS:
void BatchCreateCPUCaches();
void BatchCreateCPUCaches(const QList<game_info>& game_data = QList<game_info>{});
void BatchRemovePPUCaches();
void BatchRemoveSPUCaches();
void BatchRemoveCustomConfigurations();
@ -92,6 +94,31 @@ Q_SIGNALS:
void RequestIconSizeChange(const int& val);
void NotifyEmuSettingsChange();
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:
/** Override inherited method from Qt to allow signalling when close happened.*/
void closeEvent(QCloseEvent* event) override;
@ -168,6 +195,7 @@ private:
const std::array<int, 1> m_parsing_threads{0};
QFutureWatcher<void> m_parsing_watcher;
QFutureWatcher<void> m_refresh_watcher;
usz m_refresh_counter = 0;
QSet<QString> m_hidden_list;
bool m_show_hidden{false};
@ -185,4 +213,5 @@ private:
bool m_draw_compat_status_to_grid = false;
bool m_show_custom_icons = 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 <charconv>
#include <unordered_set>
#include <QScreen>
#include <QDirIterator>
@ -1046,7 +1047,8 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
if (success)
{
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++)
{
@ -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
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;
}
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);
return true;
}
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())
// Try to claim operaions on ID
for (auto it = paths.begin(); it != paths.end();)
{
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);
break;
it = paths.erase(it);
}
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
@ -2282,6 +2246,104 @@ void main_window::ShowTitleBars(bool show) const
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()
{
ui->exitAct->setShortcuts(QKeySequence::Quit);
@ -2344,17 +2406,7 @@ void main_window::CreateConnects()
// 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);
if (!paths.isEmpty())
{
Emu.GracefulShutdown(false);
for (const QString& path : paths)
{
AddGamesFromDir(path);
}
m_game_list_frame->Refresh(true);
}
AddGamesFromDirs(paths);
});
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->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->batchRemoveSPUCachesAct, &QAction::triggered, m_game_list_frame, &game_list_frame::BatchRemoveSPUCaches);
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)
@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;
}
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)
{
for (const auto& path : drop_paths)
{
AddGamesFromDir(path);
}
m_game_list_frame->Refresh(true);
AddGamesFromDirs(drop_paths);
break;
}
case drop_type::drop_game: // import valid games to gamelist (games.yaml)

View File

@ -150,6 +150,7 @@ private:
void CreateDockWindows();
void EnableMenus(bool enabled) 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);
@ -165,7 +166,7 @@ private:
u64 m_drop_file_timestamp = umax;
drop_type m_drop_file_cached_drop_type = drop_type::drop_error;
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);
void BootRecentAction(const QAction* act);