mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
167 lines
4.5 KiB
C++
167 lines
4.5 KiB
C++
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||
|
|
|
||
|
|
#include "alakarterunner.h"
|
||
|
|
|
||
|
|
#include <KLocalizedString>
|
||
|
|
#include <QFile>
|
||
|
|
#include <QIcon>
|
||
|
|
#include <QJsonArray>
|
||
|
|
#include <QJsonDocument>
|
||
|
|
#include <QJsonObject>
|
||
|
|
#include <QProcess>
|
||
|
|
#include <QStandardPaths>
|
||
|
|
|
||
|
|
K_PLUGIN_CLASS_WITH_JSON(AlakarteRunner, "plasma-runner-alakarte.json")
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle Steam URLs
|
||
|
|
if (command.startsWith(QLatin1String("steam://"))) {
|
||
|
|
QProcess::startDetached(QStringLiteral("xdg-open"), {command});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle other commands
|
||
|
|
QStringList args = QProcess::splitCommand(command);
|
||
|
|
if (!args.isEmpty()) {
|
||
|
|
QString program = args.takeFirst();
|
||
|
|
QProcess::startDetached(program, args);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#include "alakarterunner.moc"
|