Batch create/remove steam shortcuts

This commit is contained in:
Megamouse 2026-03-19 19:51:24 +01:00
parent 410660627d
commit c6407b37a5
4 changed files with 113 additions and 50 deletions

View file

@ -846,16 +846,19 @@ bool game_list_actions::RemoveContentList(const std::string& serial, bool is_int
RemoveContentBySerial(rpcs3::utils::get_icons_dir(), serial, "icons");
}
// Remove shortcuts in "games/shortcuts" folder and from desktop / start menu (if any)
// Remove shortcuts in "games/shortcuts" folder and from desktop / start menu / Steam (if any)
if (content_types & SHORTCUTS)
{
if (const auto it = m_content_info.name_list.find(serial); it != m_content_info.name_list.cend())
{
std::vector<std::pair<std::string, std::string>> games;
for (const std::string& name : it->second)
{
// Remove all shortcuts
gui::utils::remove_shortcuts(name, serial);
games.push_back(std::pair(name, serial));
}
// Batch remove all shortcuts
gui::utils::batch_remove_shortcuts(games);
}
}
@ -1452,15 +1455,7 @@ void game_list_actions::CreateShortcuts(const std::vector<game_info>& games, con
return;
}
bool success = true;
for (const game_info& gameinfo : games)
{
if (!gui::utils::create_shortcuts(gameinfo, locations))
{
success = false;
}
}
const bool success = gui::utils::batch_create_shortcuts(games, locations);
#ifdef _WIN32
if (locations.size() == 1 && locations.contains(gui::utils::shortcut_location::rpcs3_shortcuts))

View file

@ -81,7 +81,7 @@ namespace gui::utils
return false;
}
if (!gui::utils::create_square_pixmap(icon, size))
if (!create_square_pixmap(icon, size))
{
sys_log.error("Failed to create shortcut. Icon empty.");
return false;
@ -109,6 +109,12 @@ namespace gui::utils
return true;
}
static std::string make_simple_name(const std::string& name)
{
const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString();
return simple_name;
}
bool create_shortcut(const std::string& name,
const std::string& path,
[[maybe_unused]] const std::string& serial,
@ -118,6 +124,7 @@ namespace gui::utils
[[maybe_unused]] const std::string& target_icon_dir,
const std::string& src_banner_path,
shortcut_location location,
std::shared_ptr<steam_shortcut> steam_sc,
std::shared_ptr<iso_archive> archive)
{
if (name.empty())
@ -127,7 +134,7 @@ namespace gui::utils
}
// Remove illegal characters from filename
const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString();
const std::string simple_name = make_simple_name(name);
if (simple_name.empty() || simple_name == "." || simple_name == "..")
{
sys_log.error("Failed to create shortcut: Cleaned file name empty or not allowed");
@ -194,9 +201,8 @@ namespace gui::utils
if (location == shortcut_location::steam)
{
sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args);
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive);
return steam_sc.write_file();
ensure(steam_sc)->add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive);
return true;
}
sys_log.notice("Creating %s shortcut '%s' with arguments '%s' and .ico dir '%s'", location, link_path, target_cli_args, target_icon_dir);
@ -381,9 +387,8 @@ namespace gui::utils
if (location == shortcut_location::steam)
{
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive);
return steam_sc.write_file();
ensure(steam_sc)->add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive);
return true;
}
return true;
@ -410,9 +415,8 @@ namespace gui::utils
{
sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args);
const std::string working_dir{fs::get_executable_dir()};
steam_shortcut steam_sc{};
steam_sc.add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive);
return steam_sc.write_file();
ensure(steam_sc)->add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive);
return true;
}
fmt::append(link_path, "/%s.desktop", simple_name);
@ -466,7 +470,8 @@ namespace gui::utils
}
bool create_shortcuts(const std::shared_ptr<gui_game_info>& game,
const std::set<gui::utils::shortcut_location>& locations)
const std::set<shortcut_location>& locations,
std::shared_ptr<steam_shortcut> steam_sc)
{
if (!game || locations.empty()) return false;
@ -530,11 +535,11 @@ namespace gui::utils
const std::string cli_arg_token = is_vsh ? "RPCS3_VFS" : "RPCS3_GAMEID";
const std::string cli_arg_value = is_vsh ? ("dev_flash/" + game->info.path.substr(dev_flash.size())) : gameid_token_value;
for (gui::utils::shortcut_location location : locations)
for (shortcut_location location : locations)
{
std::string banner_path;
if (location == gui::utils::shortcut_location::steam)
if (location == shortcut_location::steam)
{
// Try to find a nice banner for steam
const std::string sfo_dir = is_iso ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path);
@ -550,19 +555,27 @@ namespace gui::utils
}
#ifdef __linux__
const std::string percent = location == gui::utils::shortcut_location::steam ? "%" : "%%";
const std::string percent = location == shortcut_location::steam ? "%" : "%%";
#else
const std::string percent = "%";
#endif
const std::string target_cli_args = fmt::format("--no-gui \"%s%s%s:%s\"", percent, cli_arg_token, percent, cli_arg_value);
if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, archive))
if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, steam_sc, archive))
{
sys_log.success("Created %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified());
if (location == shortcut_location::steam)
{
// Creation is done in caller
sys_log.notice("Prepared %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified());
}
else
{
sys_log.success("Created %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified());
}
}
else
{
sys_log.error("Failed to create %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified());
sys_log.error("Failed to create %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified());
success = false;
}
}
@ -570,9 +583,36 @@ namespace gui::utils
return success;
}
void remove_shortcuts(const std::string& name, [[maybe_unused]] const std::string& serial)
bool batch_create_shortcuts(const std::vector<std::shared_ptr<gui_game_info>>& games,
const std::set<shortcut_location>& locations)
{
if (games.empty() || locations.empty()) return false;
std::shared_ptr<steam_shortcut> steam_sc;
if (locations.contains(shortcut_location::steam))
{
// Batch steam shortcut creation
steam_sc = std::make_shared<steam_shortcut>();
}
bool result = true;
for (const auto& game : games)
{
result &= create_shortcuts(game, locations, steam_sc);
}
if (steam_sc)
{
result &= steam_sc->write_file();
}
return result;
}
static void remove_shortcuts(const std::string& simple_name, [[maybe_unused]] const std::string& serial)
{
const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString();
if (simple_name.empty() || simple_name == "." || simple_name == "..")
{
sys_log.error("Failed to remove shortcuts: Cleaned file name empty or not allowed");
@ -611,7 +651,7 @@ namespace gui::utils
std::vector<shortcut_location> locations = {
shortcut_location::desktop,
shortcut_location::applications,
shortcut_location::steam,
//shortcut_location::steam, // Handled separately
};
#ifdef _WIN32
locations.push_back(shortcut_location::rpcs3_shortcuts);
@ -631,18 +671,8 @@ namespace gui::utils
link_path += "/RPCS3";
break;
case shortcut_location::steam:
{
const std::string exe_path = fs::get_executable_path();
const std::string working_dir = fs::get_executable_dir();
steam_shortcut steam_sc{};
steam_sc.remove_shortcut(simple_name, exe_path, working_dir);
if (!steam_sc.write_file())
{
sys_log.error("Failed to remove steam shortcut for '%s'", simple_name);
}
continue;
}
fmt::throw_exception("Steam shortcuts should be removed in batch");
break;
#ifdef _WIN32
case shortcut_location::rpcs3_shortcuts:
link_path = rpcs3::utils::get_games_shortcuts_dir();
@ -665,4 +695,32 @@ namespace gui::utils
const std::string icon_path = fmt::format("%sIcons/game_icons/%s/shortcut.%s", fs::get_config_dir(), serial, icon_extension);
remove_path(icon_path, true);
}
void batch_remove_shortcuts(const std::vector<std::pair<std::string /*name*/, std::string /*serial*/>>& games)
{
if (games.empty()) return;
// Batch steam shortcut removal
const std::string exe_path = fs::get_executable_path();
const std::string working_dir = fs::get_executable_dir();
const bool is_steam_installed = steam_shortcut::steam_installed();
steam_shortcut steam_sc{};
for (const auto& [name, serial] : games)
{
const std::string simple_name = make_simple_name(name);
remove_shortcuts(simple_name, serial);
if (is_steam_installed)
{
steam_sc.remove_shortcut(simple_name, exe_path, working_dir);
}
}
if (is_steam_installed && !steam_sc.write_file())
{
sys_log.error("Failed to remove steam shortcuts");
}
}
}

View file

@ -5,6 +5,8 @@ class iso_archive;
namespace gui::utils
{
class steam_shortcut;
enum class shortcut_location
{
desktop,
@ -24,10 +26,11 @@ namespace gui::utils
const std::string& target_icon_dir,
const std::string& src_banner_path,
shortcut_location shortcut_location,
std::shared_ptr<steam_shortcut> steam_sc = nullptr,
std::shared_ptr<iso_archive> archive = nullptr);
bool create_shortcuts(const std::shared_ptr<gui_game_info>& game,
const std::set<gui::utils::shortcut_location>& locations);
bool batch_create_shortcuts(const std::vector<std::shared_ptr<gui_game_info>>& games,
const std::set<shortcut_location>& locations);
void remove_shortcuts(const std::string& name, const std::string& serial);
void batch_remove_shortcuts(const std::vector<std::pair<std::string /*name*/, std::string /*serial*/>>& games);
}

View file

@ -346,7 +346,9 @@ namespace gui::utils
const auto launch_options = entry.value<std::string>("LaunchOptions");
const auto icon = entry.value<std::string>("icon");
if (appid.has_value() && appid.value() == it->appid &&
const bool appid_matches = appid.has_value() && appid.value() == it->appid;
if (appid_matches &&
exe.has_value() && exe.value() == it->exe &&
start_dir.has_value() && start_dir.value() == it->start_dir &&
launch_options.has_value() && launch_options.value() == it->launch_options &&
@ -357,6 +359,11 @@ namespace gui::utils
}
else
{
if (appid_matches)
{
sys_log.notice("Entry '%s' already exists in steam shortcut file '%s' but with other parameters. Creating an additional one...", it->app_name, file_path);
}
it++;
}
}
@ -369,7 +376,7 @@ namespace gui::utils
if (m_entries_to_add.empty() && removed_entries.empty())
{
sys_log.notice("No matching entries found in steam shortcut file '%s'.", file_path);
sys_log.notice("Nothing to add or remove in steam shortcut file '%s'.", file_path);
return true;
}