a-la-karte/src/desktopimporter.cpp
Marco Allegretti e468f53c91 Fix desktop and Flatpak game detection
Use KDesktopFile/KConfigGroup to parse .desktop files reliably.
This avoids Categories parsing pitfalls and improves detection of KDE
stock games. Centralize game-category matching and use stable IDs
based on the full desktop-file basename.
2026-01-20 00:12:12 +01:00

178 lines
5.5 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "desktopimporter.h"
#include <KConfigGroup>
#include <KDesktopFile>
#include <QDir>
#include <QFile>
#include <QRegularExpression>
#include <QStandardPaths>
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
{
KDesktopFile desktopFile(filePath);
KConfigGroup desktop = desktopFile.desktopGroup();
const QString type = desktop.readEntry(QStringLiteral("Type"));
if (type != QStringLiteral("Application")) {
return false;
}
// Check if hidden or not shown
if (desktop.readEntry(QStringLiteral("Hidden"), false)) {
return false;
}
if (desktop.readEntry(QStringLiteral("NoDisplay"), false)) {
return false;
}
// Check categories for game-related entries
const QStringList categories = desktop.readEntry(QStringLiteral("Categories")).split(QLatin1Char(';'), Qt::SkipEmptyParts);
return hasGameCategory(categories);
}
Game *DesktopImporter::parseDesktopFile(const QString &filePath) const
{
KDesktopFile desktopFile(filePath);
KConfigGroup desktop = desktopFile.desktopGroup();
const QString name = desktop.readEntry(QStringLiteral("Name"));
QString exec = desktop.readEntry(QStringLiteral("Exec"));
const QString icon = desktop.readEntry(QStringLiteral("Icon"));
const QString comment = desktop.readEntry(QStringLiteral("Comment"));
const QString genericName = desktop.readEntry(QStringLiteral("GenericName"));
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);
const QString desktopId = fileInfo.completeBaseName();
QString gameId = QStringLiteral("desktop_") + desktopId;
Game *game = new Game(gameId, name);
game->setLaunchCommand(exec);
game->setPlatform(platformName());
game->setPlatformId(desktopId);
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<Game *> DesktopImporter::importGames()
{
QList<Game *> 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;
}