// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2026 A-La-Karte Contributors #include "desktopimporter.h" #include #include #include #include #include DesktopImporter::DesktopImporter(QObject *parent) : PlatformImporter(parent) { } QString DesktopImporter::platformName() const { return QStringLiteral("Desktop"); } QString DesktopImporter::platformId() const { return QStringLiteral("desktop"); } bool DesktopImporter::isAvailable() const { return true; } QStringList DesktopImporter::getDesktopFilePaths() const { QStringList paths; // Standard XDG application directories QStringList appDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); // Add common system locations appDirs << QStringLiteral("/usr/share/applications"); appDirs << QStringLiteral("/usr/local/share/applications"); appDirs << expandPath(QStringLiteral("~/.local/share/applications")); // Flatpak export directories appDirs << expandPath(QStringLiteral("~/.local/share/flatpak/exports/share/applications")); appDirs << QStringLiteral("/var/lib/flatpak/exports/share/applications"); for (const QString &dir : appDirs) { QDir appDir(dir); if (appDir.exists()) { QStringList files = appDir.entryList({QStringLiteral("*.desktop")}, QDir::Files); for (const QString &file : files) { QString fullPath = appDir.absoluteFilePath(file); if (!paths.contains(fullPath)) { paths << fullPath; } } } } return paths; } bool DesktopImporter::isGameDesktopFile(const QString &filePath) const { QSettings desktop(filePath, QSettings::IniFormat); desktop.beginGroup(QStringLiteral("Desktop Entry")); QString type = desktop.value(QStringLiteral("Type")).toString(); if (type != QStringLiteral("Application")) { return false; } // Check if hidden or not shown if (desktop.value(QStringLiteral("Hidden"), false).toBool()) { return false; } if (desktop.value(QStringLiteral("NoDisplay"), false).toBool()) { return false; } // Check categories for game-related entries QString categories = desktop.value(QStringLiteral("Categories")).toString(); QStringList gameCategories = {QStringLiteral("Game"), QStringLiteral("ArcadeGame"), QStringLiteral("ActionGame"), QStringLiteral("AdventureGame"), QStringLiteral("BlocksGame"), QStringLiteral("BoardGame"), QStringLiteral("CardGame"), QStringLiteral("KidsGame"), QStringLiteral("LogicGame"), QStringLiteral("RolePlaying"), QStringLiteral("Shooter"), QStringLiteral("Simulation"), QStringLiteral("SportsGame"), QStringLiteral("StrategyGame")}; for (const QString &cat : gameCategories) { if (categories.contains(cat, Qt::CaseInsensitive)) { return true; } } return false; } Game *DesktopImporter::parseDesktopFile(const QString &filePath) const { QSettings desktop(filePath, QSettings::IniFormat); desktop.beginGroup(QStringLiteral("Desktop Entry")); QString name = desktop.value(QStringLiteral("Name")).toString(); QString exec = desktop.value(QStringLiteral("Exec")).toString(); QString icon = desktop.value(QStringLiteral("Icon")).toString(); QString comment = desktop.value(QStringLiteral("Comment")).toString(); QString genericName = desktop.value(QStringLiteral("GenericName")).toString(); if (name.isEmpty() || exec.isEmpty()) { return nullptr; } // Clean up the exec command - remove field codes like %f, %u, %F, %U static QRegularExpression fieldCodeRegex(QStringLiteral("%[fFuUdDnNickvm]")); exec = exec.remove(fieldCodeRegex).trimmed(); // Create unique ID from the desktop file name QFileInfo fileInfo(filePath); QString gameId = QStringLiteral("desktop_") + fileInfo.baseName(); Game *game = new Game(gameId, name); game->setLaunchCommand(exec); game->setPlatform(platformName()); game->setPlatformId(platformId()); if (!comment.isEmpty()) { game->setDescription(comment); } else if (!genericName.isEmpty()) { game->setDescription(genericName); } // Try to find icon if (!icon.isEmpty()) { // If it's an absolute path, use it directly if (QFile::exists(icon)) { game->setIconUrl(QUrl::fromLocalFile(icon)); } else { // Try to find in standard icon locations QStringList iconPaths = { expandPath(QStringLiteral("~/.local/share/icons/hicolor/256x256/apps/") + icon + QStringLiteral(".png")), QStringLiteral("/usr/share/icons/hicolor/256x256/apps/") + icon + QStringLiteral(".png"), QStringLiteral("/usr/share/pixmaps/") + icon + QStringLiteral(".png"), expandPath(QStringLiteral("~/.local/share/icons/hicolor/128x128/apps/") + icon + QStringLiteral(".png")), QStringLiteral("/usr/share/icons/hicolor/128x128/apps/") + icon + QStringLiteral(".png"), }; for (const QString &iconPath : iconPaths) { if (QFile::exists(iconPath)) { const QUrl url = QUrl::fromLocalFile(iconPath); game->setIconUrl(url); if (!game->coverUrl().isValid() || game->coverUrl().isEmpty()) { game->setCoverUrl(url); } break; } } } } return game; } QList DesktopImporter::importGames() { QList games; QStringList desktopFiles = getDesktopFilePaths(); int total = desktopFiles.count(); int current = 0; for (const QString &filePath : desktopFiles) { current++; Q_EMIT importProgress(current, total); if (!isGameDesktopFile(filePath)) { continue; } Game *game = parseDesktopFile(filePath); if (game) { games.append(game); } } return games; }