mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-26 17:03:08 +00:00
190 lines
5.5 KiB
C++
190 lines
5.5 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
|
|
|
#include "alakarterunner.h"
|
|
|
|
#include <KLocalizedString>
|
|
#include <QDBusConnection>
|
|
#include <QDBusInterface>
|
|
#include <QDBusReply>
|
|
#include <QFile>
|
|
#include <QIcon>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QProcess>
|
|
#include <QStandardPaths>
|
|
|
|
K_PLUGIN_CLASS_WITH_JSON(AlakarteRunner, "plasma-runner-alakarte.json")
|
|
|
|
static const QString kGameCenterService = QStringLiteral("org.kde.GameCenter1");
|
|
static const QString kGameCenterPath = QStringLiteral("/org/kde/GameCenter1");
|
|
static const QString kGameCenterInterface = QStringLiteral("org.kde.GameCenter1");
|
|
|
|
static bool launchViaDaemon(QDBusConnection bus, const QVariantMap &launchSpec)
|
|
{
|
|
if (!bus.isConnected()) {
|
|
return false;
|
|
}
|
|
|
|
QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, bus);
|
|
if (!iface.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
iface.setTimeout(2000);
|
|
const QDBusReply<QString> reply = iface.call(QStringLiteral("Launch"), launchSpec);
|
|
return reply.isValid() && !reply.value().isEmpty();
|
|
}
|
|
|
|
AlakarteRunner::AlakarteRunner(QObject *parent, const KPluginMetaData &metaData)
|
|
: KRunner::AbstractRunner(parent, metaData)
|
|
{
|
|
addSyntax(QStringLiteral("game <query>"), i18n("Search and launch games from A-La-Karte library"));
|
|
addSyntax(QStringLiteral("<query>"), i18n("Search games by name"));
|
|
}
|
|
|
|
AlakarteRunner::~AlakarteRunner() = default;
|
|
|
|
QString AlakarteRunner::libraryPath() const
|
|
{
|
|
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).replace(QStringLiteral("krunner_alakarte"), QStringLiteral("alakarte"))
|
|
+ QStringLiteral("/library.json");
|
|
}
|
|
|
|
QList<AlakarteRunner::GameInfo> AlakarteRunner::loadGames() const
|
|
{
|
|
QList<GameInfo> games;
|
|
|
|
QFile file(libraryPath());
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
return games;
|
|
}
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|
if (!doc.isArray()) {
|
|
return games;
|
|
}
|
|
|
|
QJsonArray array = doc.array();
|
|
for (const QJsonValue &value : array) {
|
|
QJsonObject obj = value.toObject();
|
|
|
|
// Skip hidden games
|
|
if (obj[QStringLiteral("hidden")].toBool()) {
|
|
continue;
|
|
}
|
|
|
|
GameInfo info;
|
|
info.id = obj[QStringLiteral("id")].toString();
|
|
info.name = obj[QStringLiteral("name")].toString();
|
|
info.developer = obj[QStringLiteral("developer")].toString();
|
|
info.platform = obj[QStringLiteral("platform")].toString();
|
|
info.launchCommand = obj[QStringLiteral("launchCommand")].toString();
|
|
info.coverPath = obj[QStringLiteral("coverUrl")].toString();
|
|
|
|
if (!info.name.isEmpty() && !info.launchCommand.isEmpty()) {
|
|
games.append(info);
|
|
}
|
|
}
|
|
|
|
return games;
|
|
}
|
|
|
|
void AlakarteRunner::match(KRunner::RunnerContext &context)
|
|
{
|
|
const QString query = context.query();
|
|
|
|
if (query.length() < 2) {
|
|
return;
|
|
}
|
|
|
|
QString searchTerm = query;
|
|
bool explicitGameSearch = false;
|
|
|
|
// Check for "game " prefix
|
|
if (query.startsWith(QLatin1String("game "), Qt::CaseInsensitive)) {
|
|
searchTerm = query.mid(5).trimmed();
|
|
explicitGameSearch = true;
|
|
}
|
|
|
|
if (searchTerm.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QList<GameInfo> games = loadGames();
|
|
|
|
for (const GameInfo &game : games) {
|
|
bool matches = game.name.contains(searchTerm, Qt::CaseInsensitive) || game.developer.contains(searchTerm, Qt::CaseInsensitive);
|
|
|
|
if (!matches && !explicitGameSearch) {
|
|
continue;
|
|
}
|
|
|
|
if (!matches) {
|
|
continue;
|
|
}
|
|
|
|
KRunner::QueryMatch match(this);
|
|
match.setText(game.name);
|
|
|
|
QString subtext;
|
|
if (!game.developer.isEmpty()) {
|
|
subtext = game.developer;
|
|
}
|
|
if (!game.platform.isEmpty()) {
|
|
if (!subtext.isEmpty()) {
|
|
subtext += QStringLiteral(" • ");
|
|
}
|
|
subtext += game.platform;
|
|
}
|
|
match.setSubtext(subtext);
|
|
|
|
match.setIconName(QStringLiteral("applications-games"));
|
|
match.setId(game.id);
|
|
match.setData(game.launchCommand);
|
|
|
|
// Calculate relevance
|
|
qreal relevance = 0.7;
|
|
if (game.name.startsWith(searchTerm, Qt::CaseInsensitive)) {
|
|
relevance = 0.9;
|
|
} else if (game.name.contains(searchTerm, Qt::CaseInsensitive)) {
|
|
relevance = 0.8;
|
|
}
|
|
|
|
if (explicitGameSearch) {
|
|
relevance += 0.05;
|
|
}
|
|
|
|
match.setRelevance(relevance);
|
|
match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Moderate);
|
|
|
|
context.addMatch(match);
|
|
}
|
|
}
|
|
|
|
void AlakarteRunner::run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match)
|
|
{
|
|
Q_UNUSED(context)
|
|
|
|
QString command = match.data().toString();
|
|
if (command.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Always try daemon first for all commands (including Steam/Lutris)
|
|
if (!match.id().isEmpty()) {
|
|
QVariantMap launchSpec = {
|
|
{QStringLiteral("command"), command},
|
|
{QStringLiteral("gameId"), match.id()},
|
|
{QStringLiteral("displayName"), match.text()},
|
|
{QStringLiteral("origin"), QStringLiteral("krunner")},
|
|
};
|
|
|
|
if (launchViaDaemon(QDBusConnection::systemBus(), launchSpec) || launchViaDaemon(QDBusConnection::sessionBus(), launchSpec)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "alakarterunner.moc"
|