// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2026 A-La-Karte Contributors #include "alakarterunner.h" #include #include #include #include #include #include #include #include #include #include #include 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 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 "), i18n("Search and launch games from A-La-Karte library")); addSyntax(QStringLiteral(""), 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::loadGames() const { QList 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 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"