mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-05 18:23:39 -04:00
citra_qt: add bulk shortcut creation for all games
Adds Tools > Create Shortcuts for All Games > Add to Desktop / Add to Applications Menu. Asks for fullscreen preference once, then iterates every unique game in the list with a progress dialog and shows a summary on completion. Hidden on macOS where single-game shortcut creation is also unsupported. GameList gains GetAllGames() to enumerate unique (program_id, path) pairs from the model, deduplicating entries that appear in both a directory and the Favorites section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e59960c518
commit
ba9ebb58fe
5 changed files with 182 additions and 0 deletions
|
|
@ -1214,6 +1214,17 @@ void GMainWindow::ConnectMenuEvents() {
|
|||
connect_menu(ui->action_Compress_ROM_File, &GMainWindow::OnCompressFile);
|
||||
connect_menu(ui->action_Decompress_ROM_File, &GMainWindow::OnDecompressFile);
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
connect(ui->action_Create_Shortcuts_Desktop, &QAction::triggered, [this] {
|
||||
OnGameListCreateShortcutForAllGames(GameListShortcutTarget::Desktop);
|
||||
});
|
||||
connect(ui->action_Create_Shortcuts_Applications, &QAction::triggered, [this] {
|
||||
OnGameListCreateShortcutForAllGames(GameListShortcutTarget::Applications);
|
||||
});
|
||||
#else
|
||||
ui->menu_Create_Shortcuts->menuAction()->setVisible(false);
|
||||
#endif
|
||||
|
||||
// Help
|
||||
connect_menu(ui->action_Open_Citra_Folder, &GMainWindow::OnOpenCitraFolder);
|
||||
connect_menu(ui->action_Open_Log_Folder, []() {
|
||||
|
|
@ -2187,6 +2198,130 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_ERROR, qt_game_title);
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListCreateShortcutForAllGames(GameListShortcutTarget target) {
|
||||
// Resolve emulator command (same logic as single-game shortcut)
|
||||
std::string citra_command;
|
||||
bool skip_tryexec = false;
|
||||
const char* env_flatpak_id = getenv("FLATPAK_ID");
|
||||
if (env_flatpak_id) {
|
||||
citra_command = fmt::format("flatpak run {}", env_flatpak_id);
|
||||
skip_tryexec = true;
|
||||
} else {
|
||||
const QStringList args = QApplication::arguments();
|
||||
citra_command = args[0].toStdString();
|
||||
if (citra_command.c_str()[0] == '.') {
|
||||
citra_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + citra_command;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve shortcut target directory
|
||||
std::filesystem::path shortcut_path;
|
||||
if (target == GameListShortcutTarget::Desktop) {
|
||||
shortcut_path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
|
||||
} else if (target == GameListShortcutTarget::Applications) {
|
||||
shortcut_path = GetApplicationsDirectory();
|
||||
}
|
||||
if (!std::filesystem::exists(shortcut_path)) {
|
||||
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_ERROR, {});
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
// Warn once about volatile AppImage shortcuts
|
||||
const std::string appimage_ending =
|
||||
std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
|
||||
if (citra_command.ends_with(appimage_ending) && !UISettings::values.shortcut_already_warned) {
|
||||
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING, {})) {
|
||||
return;
|
||||
}
|
||||
UISettings::values.shortcut_already_warned = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Collect all unique games from the list
|
||||
const auto games = game_list->GetAllGames();
|
||||
if (games.isEmpty()) {
|
||||
QMessageBox::information(this, tr("Create Shortcuts"),
|
||||
tr("No games found in the game list."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask about fullscreen once for all games
|
||||
const bool fullscreen =
|
||||
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT, {});
|
||||
|
||||
QProgressDialog progress(tr("Creating shortcuts..."), tr("Cancel"), 0, games.size(), this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(0);
|
||||
|
||||
int success_count = 0;
|
||||
int fail_count = 0;
|
||||
|
||||
for (int i = 0; i < games.size(); ++i) {
|
||||
if (progress.wasCanceled())
|
||||
break;
|
||||
progress.setValue(i);
|
||||
|
||||
const u64 program_id = games[i].first;
|
||||
const std::string game_path = games[i].second.toStdString();
|
||||
|
||||
// Read title
|
||||
const auto loader = Loader::GetLoader(game_path);
|
||||
std::string game_title = fmt::format("{:016X}", program_id);
|
||||
if (loader->ReadTitle(game_title) != Loader::ResultStatus::Success) {
|
||||
game_title = fmt::format("{:016x}", program_id);
|
||||
}
|
||||
progress.setLabelText(
|
||||
tr("Creating shortcut for %1...").arg(QString::fromStdString(game_title)));
|
||||
|
||||
// Remove illegal filename characters
|
||||
const std::string illegal_chars = "<>:\"/\\|?*.";
|
||||
for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
|
||||
if (illegal_chars.find(*it) != std::string::npos) {
|
||||
game_title.erase(it.base() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Write icon
|
||||
std::vector<u8> icon_image_file;
|
||||
loader->ReadIcon(icon_image_file);
|
||||
const QImage icon_data = GetQPixmapFromSMDH(icon_image_file).toImage();
|
||||
std::filesystem::path out_icon_path;
|
||||
if (MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
|
||||
if (!SaveIconToFile(out_icon_path, icon_data)) {
|
||||
LOG_WARNING(Frontend, "Could not write icon for {:s}", game_path);
|
||||
}
|
||||
}
|
||||
|
||||
std::string arguments = fmt::format("\"{:s}\"", game_path);
|
||||
if (fullscreen) {
|
||||
arguments = "-f " + arguments;
|
||||
}
|
||||
const std::string comment =
|
||||
fmt::format("Start {:s} with the Azahar Emulator", game_title);
|
||||
|
||||
if (CreateShortcutLink(shortcut_path, comment, out_icon_path, citra_command, arguments,
|
||||
"Game;Emulator;Qt;", "3ds;Nintendo;", game_title, skip_tryexec)) {
|
||||
++success_count;
|
||||
} else {
|
||||
++fail_count;
|
||||
}
|
||||
}
|
||||
progress.setValue(games.size());
|
||||
|
||||
if (fail_count == 0) {
|
||||
QMessageBox::information(
|
||||
this, tr("Create Shortcuts"),
|
||||
tr("Successfully created %n shortcut(s).", "", success_count));
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Create Shortcuts"),
|
||||
tr("Created %1 shortcut(s). %2 could not be created.")
|
||||
.arg(success_count)
|
||||
.arg(fail_count));
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListDumpRomFS(QString game_path, u64 program_id) {
|
||||
auto* dialog = new QProgressDialog(tr("Dumping..."), tr("Cancel"), 0, 0, this);
|
||||
dialog->setWindowModality(Qt::WindowModal);
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ private slots:
|
|||
void OnGameListRemovePlayTimeData(u64 program_id);
|
||||
void OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
|
||||
GameListShortcutTarget target);
|
||||
void OnGameListCreateShortcutForAllGames(GameListShortcutTarget target);
|
||||
void OnGameListDumpRomFS(QString game_path, u64 program_id);
|
||||
void OnGameListOpenDirectory(const QString& directory);
|
||||
void OnGameListAddDirectory();
|
||||
|
|
|
|||
|
|
@ -1079,6 +1079,30 @@ QStandardItemModel* GameList::GetModel() const {
|
|||
return item_model;
|
||||
}
|
||||
|
||||
QVector<QPair<u64, QString>> GameList::GetAllGames() const {
|
||||
QVector<QPair<u64, QString>> games;
|
||||
QSet<QString> seen;
|
||||
|
||||
std::function<void(const QModelIndex&)> collect = [&](const QModelIndex& parent) {
|
||||
for (int i = 0; i < item_model->rowCount(parent); ++i) {
|
||||
const QModelIndex idx = item_model->index(i, 0, parent);
|
||||
const auto type =
|
||||
static_cast<GameListItemType>(idx.data(GameListItem::TypeRole).toInt());
|
||||
if (type == GameListItemType::Game) {
|
||||
const QString path = idx.data(GameListItemPath::FullPathRole).toString();
|
||||
if (!seen.contains(path)) {
|
||||
seen.insert(path);
|
||||
games.append({idx.data(GameListItemPath::ProgramIdRole).toULongLong(), path});
|
||||
}
|
||||
} else {
|
||||
collect(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
collect(QModelIndex());
|
||||
return games;
|
||||
}
|
||||
|
||||
void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
||||
tree_view->setEnabled(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ public:
|
|||
|
||||
QString FindGameByProgramID(u64 program_id, int role);
|
||||
|
||||
/// Returns all unique games currently in the list as (program_id, path) pairs.
|
||||
QVector<QPair<u64, QString>> GetAllGames() const;
|
||||
|
||||
void RefreshGameDirectory();
|
||||
|
||||
void ToggleFavorite(u64 program_id);
|
||||
|
|
|
|||
|
|
@ -221,6 +221,15 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="action_Compress_ROM_File"/>
|
||||
<addaction name="action_Decompress_ROM_File"/>
|
||||
<addaction name="separator"/>
|
||||
<widget class="QMenu" name="menu_Create_Shortcuts">
|
||||
<property name="title">
|
||||
<string>Create Shortcuts for All Games</string>
|
||||
</property>
|
||||
<addaction name="action_Create_Shortcuts_Desktop"/>
|
||||
<addaction name="action_Create_Shortcuts_Applications"/>
|
||||
</widget>
|
||||
<addaction name="menu_Create_Shortcuts"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Help">
|
||||
<property name="title">
|
||||
|
|
@ -505,6 +514,16 @@
|
|||
<string>Decompress ROM File...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Create_Shortcuts_Desktop">
|
||||
<property name="text">
|
||||
<string>Add to Desktop</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Create_Shortcuts_Applications">
|
||||
<property name="text">
|
||||
<string>Add to Applications Menu</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_View_Lobby">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue