mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-09 13:03:09 +00:00
Initial release of A-La-Karte, a unified game launcher for KDE Plasma. Includes the QML UI, platform importers, AppStream metadata, icons, and developer documentation.
196 lines
6.5 KiB
C++
196 lines
6.5 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
|
|
|
#include "desktopimporter.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QRegularExpression>
|
|
#include <QSettings>
|
|
#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
|
|
{
|
|
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<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;
|
|
}
|