a-la-karte/src/flatpakimporter.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

151 lines
4.4 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "flatpakimporter.h"
#include <KConfigGroup>
#include <KDesktopFile>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QRegularExpression>
FlatpakImporter::FlatpakImporter(QObject *parent)
: PlatformImporter(parent)
{
}
QString FlatpakImporter::platformName() const
{
return QStringLiteral("Flatpak");
}
QString FlatpakImporter::platformId() const
{
return QStringLiteral("flatpak");
}
QStringList FlatpakImporter::getFlatpakPaths() const
{
QStringList paths;
// User Flatpak exports
paths << expandPath(QStringLiteral("~/.local/share/flatpak/exports/share/applications"));
// System Flatpak exports
paths << QStringLiteral("/var/lib/flatpak/exports/share/applications");
return paths;
}
bool FlatpakImporter::isAvailable() const
{
return !findExecutable(QStringLiteral("flatpak")).isEmpty();
}
bool FlatpakImporter::isGameApp(const QString &desktopFilePath) const
{
KDesktopFile desktopFile(desktopFilePath);
KConfigGroup desktop = desktopFile.desktopGroup();
const QString type = desktop.readEntry(QStringLiteral("Type"));
if (type != QStringLiteral("Application")) {
return false;
}
if (desktop.readEntry(QStringLiteral("Hidden"), false)) {
return false;
}
if (desktop.readEntry(QStringLiteral("NoDisplay"), false)) {
return false;
}
const QStringList categories = desktop.readEntry(QStringLiteral("Categories")).split(QLatin1Char(';'), Qt::SkipEmptyParts);
return hasGameCategory(categories);
}
Game *FlatpakImporter::parseDesktopFile(const QString &filePath, const QString &appId) const
{
KDesktopFile desktopFile(filePath);
KConfigGroup desktop = desktopFile.desktopGroup();
const QString name = desktop.readEntry(QStringLiteral("Name"));
const QString icon = desktop.readEntry(QStringLiteral("Icon"));
const QString comment = desktop.readEntry(QStringLiteral("Comment"));
if (name.isEmpty()) {
return nullptr;
}
QString gameId = QStringLiteral("flatpak_") + appId.toLower().replace(QStringLiteral("."), QStringLiteral("_"));
Game *game = new Game(gameId, name);
game->setPlatform(platformName());
game->setPlatformId(appId);
game->setLaunchCommand(QStringLiteral("flatpak run %1").arg(appId));
if (!comment.isEmpty()) {
game->setDescription(comment);
}
// Try to find icon
if (!icon.isEmpty()) {
QStringList iconPaths = {
expandPath(QStringLiteral("~/.local/share/flatpak/exports/share/icons/hicolor/256x256/apps/") + icon + QStringLiteral(".png")),
QStringLiteral("/var/lib/flatpak/exports/share/icons/hicolor/256x256/apps/") + icon + QStringLiteral(".png"),
expandPath(QStringLiteral("~/.local/share/flatpak/exports/share/icons/hicolor/128x128/apps/") + icon + QStringLiteral(".png")),
QStringLiteral("/var/lib/flatpak/exports/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 *> FlatpakImporter::importGames()
{
QList<Game *> games;
for (const QString &path : getFlatpakPaths()) {
QDir dir(path);
if (!dir.exists()) {
continue;
}
QStringList desktopFiles = dir.entryList({QStringLiteral("*.desktop")}, QDir::Files);
int total = desktopFiles.count();
int current = 0;
for (const QString &fileName : desktopFiles) {
current++;
Q_EMIT importProgress(current, total);
QString filePath = dir.absoluteFilePath(fileName);
if (!isGameApp(filePath)) {
continue;
}
// Extract app ID from filename (e.g., "com.example.Game.desktop" -> "com.example.Game")
QString appId = fileName;
appId.chop(8); // Remove ".desktop"
Game *game = parseDesktopFile(filePath, appId);
if (game) {
games.append(game);
}
}
}
return games;
}