mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-26 17:03:08 +00:00
runner: add game profile API
This commit is contained in:
parent
27a385e3c3
commit
7724205354
4 changed files with 633 additions and 6 deletions
|
|
@ -4,10 +4,12 @@
|
|||
#include "runnermanagerdaemon.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusError>
|
||||
#include <QDBusMessage>
|
||||
#include <QDBusReply>
|
||||
#include <QDBusVariant>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QEventLoop>
|
||||
|
|
@ -32,6 +34,52 @@
|
|||
static const QString kRunnerService = QStringLiteral("org.kde.ALaKarte.Runner1");
|
||||
static const QString kRunnerPath = QStringLiteral("/org/kde/ALaKarte/Runner1");
|
||||
|
||||
static QVariant unwrapDbusVariant(QVariant v)
|
||||
{
|
||||
if (v.metaType() == QMetaType::fromType<QDBusVariant>()) {
|
||||
v = v.value<QDBusVariant>().variant();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static QVariantMap unwrapVariantMap(QVariant v)
|
||||
{
|
||||
v = unwrapDbusVariant(v);
|
||||
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||
return qdbus_cast<QVariantMap>(arg);
|
||||
}
|
||||
if (v.canConvert<QVariantMap>()) {
|
||||
return v.toMap();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static QStringList unwrapStringList(QVariant v)
|
||||
{
|
||||
v = unwrapDbusVariant(v);
|
||||
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||
return qdbus_cast<QStringList>(arg);
|
||||
}
|
||||
if (v.canConvert<QStringList>()) {
|
||||
return v.toStringList();
|
||||
}
|
||||
if (v.canConvert<QVariantList>()) {
|
||||
const QVariantList list = v.toList();
|
||||
QStringList out;
|
||||
out.reserve(list.size());
|
||||
for (const QVariant &item : list) {
|
||||
const QString s = item.toString();
|
||||
if (!s.isEmpty()) {
|
||||
out.push_back(s);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static QString stableIdForPath(const QString &prefix, const QString &path)
|
||||
{
|
||||
const QByteArray digest = QCryptographicHash::hash(path.toUtf8(), QCryptographicHash::Sha1).toHex();
|
||||
|
|
@ -46,6 +94,7 @@ RunnerManagerDaemon::RunnerManagerDaemon(QObject *parent)
|
|||
bool RunnerManagerDaemon::init()
|
||||
{
|
||||
loadRegistry();
|
||||
loadGameProfiles();
|
||||
m_discovered = discoverRunners();
|
||||
|
||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
|
|
@ -181,6 +230,76 @@ bool RunnerManagerDaemon::saveRegistry() const
|
|||
return true;
|
||||
}
|
||||
|
||||
QString RunnerManagerDaemon::gameProfilesPath() const
|
||||
{
|
||||
const QString base = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
return base + QStringLiteral("/runner-game-profiles.json");
|
||||
}
|
||||
|
||||
void RunnerManagerDaemon::loadGameProfiles()
|
||||
{
|
||||
m_gameProfiles.clear();
|
||||
|
||||
QFile f(gameProfilesPath());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll());
|
||||
if (!doc.isObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonArray arr = doc.object().value(QStringLiteral("profiles")).toArray();
|
||||
for (const QJsonValue &v : arr) {
|
||||
if (!v.isObject()) {
|
||||
continue;
|
||||
}
|
||||
const QVariantMap map = v.toObject().toVariantMap();
|
||||
const QString gameId = map.value(QStringLiteral("gameId")).toString();
|
||||
if (gameId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
m_gameProfiles.insert(gameId, map);
|
||||
}
|
||||
}
|
||||
|
||||
bool RunnerManagerDaemon::saveGameProfiles() const
|
||||
{
|
||||
const QString path = gameProfilesPath();
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
|
||||
QJsonArray arr;
|
||||
for (auto it = m_gameProfiles.constBegin(); it != m_gameProfiles.constEnd(); ++it) {
|
||||
QVariantMap profile = it.value();
|
||||
if (!profile.contains(QStringLiteral("gameId"))) {
|
||||
profile.insert(QStringLiteral("gameId"), it.key());
|
||||
}
|
||||
arr.push_back(QJsonObject::fromVariantMap(profile));
|
||||
}
|
||||
|
||||
QJsonObject root;
|
||||
root.insert(QStringLiteral("profiles"), arr);
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
f.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap RunnerManagerDaemon::gameProfileForGameId(const QString &gameId) const
|
||||
{
|
||||
const QString id = gameId.trimmed();
|
||||
if (id.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto it = m_gameProfiles.constFind(id);
|
||||
return it == m_gameProfiles.constEnd() ? QVariantMap{} : it.value();
|
||||
}
|
||||
|
||||
QStringList RunnerManagerDaemon::steamCandidateRoots()
|
||||
{
|
||||
const QString home = QDir::homePath();
|
||||
|
|
@ -998,14 +1117,192 @@ QVariantMap RunnerManagerDaemon::DeletePrefix(const QVariantMap &spec)
|
|||
return out;
|
||||
}
|
||||
|
||||
QVariantMap RunnerManagerDaemon::GetGameProfile(const QString &gameId) const
|
||||
{
|
||||
QVariantMap out;
|
||||
out.insert(QStringLiteral("ok"), false);
|
||||
|
||||
const QString id = gameId.trimmed();
|
||||
if (id.isEmpty()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("missing gameId"));
|
||||
return out;
|
||||
}
|
||||
|
||||
const QVariantMap profile = gameProfileForGameId(id);
|
||||
out.insert(QStringLiteral("ok"), true);
|
||||
out.insert(QStringLiteral("profile"), profile);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantList RunnerManagerDaemon::ListGameProfiles() const
|
||||
{
|
||||
QVariantList out;
|
||||
out.reserve(m_gameProfiles.size());
|
||||
for (auto it = m_gameProfiles.constBegin(); it != m_gameProfiles.constEnd(); ++it) {
|
||||
QVariantMap profile = it.value();
|
||||
if (!profile.contains(QStringLiteral("gameId"))) {
|
||||
profile.insert(QStringLiteral("gameId"), it.key());
|
||||
}
|
||||
out.push_back(profile);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap RunnerManagerDaemon::SetGameProfile(const QVariantMap &spec)
|
||||
{
|
||||
QVariantMap out;
|
||||
out.insert(QStringLiteral("ok"), false);
|
||||
|
||||
const QString gameId = spec.value(QStringLiteral("gameId")).toString().trimmed();
|
||||
if (gameId.isEmpty()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("missing gameId"));
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap profile;
|
||||
profile.insert(QStringLiteral("gameId"), gameId);
|
||||
|
||||
auto takeStringIfPresent = [&](const QString &key) {
|
||||
if (!spec.contains(key)) {
|
||||
return;
|
||||
}
|
||||
const QString v = spec.value(key).toString().trimmed();
|
||||
if (!v.isEmpty()) {
|
||||
profile.insert(key, v);
|
||||
}
|
||||
};
|
||||
|
||||
takeStringIfPresent(QStringLiteral("runnerId"));
|
||||
takeStringIfPresent(QStringLiteral("runner"));
|
||||
takeStringIfPresent(QStringLiteral("runnerPath"));
|
||||
takeStringIfPresent(QStringLiteral("prefixPath"));
|
||||
|
||||
if (spec.contains(QStringLiteral("dllOverrides"))) {
|
||||
const QVariantMap raw = unwrapVariantMap(spec.value(QStringLiteral("dllOverrides")));
|
||||
QVariantMap dllOverrides;
|
||||
for (auto it = raw.constBegin(); it != raw.constEnd(); ++it) {
|
||||
const QString key = it.key().trimmed();
|
||||
if (key.isEmpty() || key.contains(QLatin1Char('=')) || key.contains(QLatin1Char(';'))) {
|
||||
continue;
|
||||
}
|
||||
const QString value = it.value().toString().trimmed();
|
||||
if (value.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
dllOverrides.insert(key, value);
|
||||
}
|
||||
if (!dllOverrides.isEmpty()) {
|
||||
profile.insert(QStringLiteral("dllOverrides"), dllOverrides);
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.contains(QStringLiteral("envOverrides"))) {
|
||||
const QVariantMap rawEnv = unwrapVariantMap(spec.value(QStringLiteral("envOverrides")));
|
||||
QVariantMap env;
|
||||
for (auto it = rawEnv.constBegin(); it != rawEnv.constEnd(); ++it) {
|
||||
const QString key = it.key();
|
||||
if (key.isEmpty() || key.contains(QLatin1Char('='))) {
|
||||
continue;
|
||||
}
|
||||
env.insert(key, it.value().toString());
|
||||
}
|
||||
if (!env.isEmpty()) {
|
||||
profile.insert(QStringLiteral("envOverrides"), env);
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.contains(QStringLiteral("extraArgs"))) {
|
||||
const QStringList raw = unwrapStringList(spec.value(QStringLiteral("extraArgs")));
|
||||
QStringList extraArgs;
|
||||
extraArgs.reserve(raw.size());
|
||||
for (const QString &a : raw) {
|
||||
const QString trimmed = a.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
extraArgs.push_back(trimmed);
|
||||
}
|
||||
}
|
||||
if (!extraArgs.isEmpty()) {
|
||||
profile.insert(QStringLiteral("extraArgs"), extraArgs);
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.size() <= 1) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("empty profile"));
|
||||
return out;
|
||||
}
|
||||
|
||||
m_gameProfiles.insert(gameId, profile);
|
||||
if (!saveGameProfiles()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("failed to save game profiles"));
|
||||
return out;
|
||||
}
|
||||
|
||||
Q_EMIT GameProfilesChanged();
|
||||
out.insert(QStringLiteral("ok"), true);
|
||||
out.insert(QStringLiteral("profile"), profile);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap RunnerManagerDaemon::ClearGameProfile(const QString &gameId)
|
||||
{
|
||||
QVariantMap out;
|
||||
out.insert(QStringLiteral("ok"), false);
|
||||
|
||||
const QString id = gameId.trimmed();
|
||||
if (id.isEmpty()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("missing gameId"));
|
||||
return out;
|
||||
}
|
||||
|
||||
if (!m_gameProfiles.contains(id)) {
|
||||
out.insert(QStringLiteral("ok"), true);
|
||||
out.insert(QStringLiteral("gameId"), id);
|
||||
return out;
|
||||
}
|
||||
|
||||
m_gameProfiles.remove(id);
|
||||
if (!saveGameProfiles()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("failed to save game profiles"));
|
||||
return out;
|
||||
}
|
||||
|
||||
Q_EMIT GameProfilesChanged();
|
||||
out.insert(QStringLiteral("ok"), true);
|
||||
out.insert(QStringLiteral("gameId"), id);
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap RunnerManagerDaemon::ResolveLaunch(const QVariantMap &spec) const
|
||||
{
|
||||
QVariantMap out;
|
||||
out.insert(QStringLiteral("ok"), false);
|
||||
|
||||
const QString runnerId = spec.value(QStringLiteral("runnerId")).toString();
|
||||
QString runner = spec.value(QStringLiteral("runner")).toString();
|
||||
QString runnerPath = spec.value(QStringLiteral("runnerPath")).toString();
|
||||
const QString gameId = spec.value(QStringLiteral("gameId")).toString();
|
||||
|
||||
const bool useGameProfile = spec.contains(QStringLiteral("useGameProfile")) ? spec.value(QStringLiteral("useGameProfile")).toBool() : false;
|
||||
|
||||
QVariantMap profile;
|
||||
if (useGameProfile && !gameId.isEmpty()) {
|
||||
profile = gameProfileForGameId(gameId);
|
||||
}
|
||||
|
||||
const QString specRunnerId = spec.value(QStringLiteral("runnerId")).toString();
|
||||
const QString specRunner = spec.value(QStringLiteral("runner")).toString();
|
||||
const QString specRunnerPath = spec.value(QStringLiteral("runnerPath")).toString();
|
||||
|
||||
QString runnerId = specRunnerId;
|
||||
QString runner = specRunner;
|
||||
QString runnerPath = specRunnerPath;
|
||||
|
||||
if (runnerId.isEmpty() && profile.contains(QStringLiteral("runnerId"))) {
|
||||
runnerId = profile.value(QStringLiteral("runnerId")).toString();
|
||||
}
|
||||
if (runner.isEmpty() && profile.contains(QStringLiteral("runner"))) {
|
||||
runner = profile.value(QStringLiteral("runner")).toString();
|
||||
}
|
||||
if (runnerPath.isEmpty() && profile.contains(QStringLiteral("runnerPath"))) {
|
||||
runnerPath = profile.value(QStringLiteral("runnerPath")).toString();
|
||||
}
|
||||
|
||||
if (!runnerId.isEmpty()) {
|
||||
const RunnerInfo info = runnerById(runnerId);
|
||||
|
|
@ -1017,17 +1314,84 @@ QVariantMap RunnerManagerDaemon::ResolveLaunch(const QVariantMap &spec) const
|
|||
runnerPath = info.path;
|
||||
}
|
||||
|
||||
const QString gameId = spec.value(QStringLiteral("gameId")).toString();
|
||||
const QString program = spec.value(QStringLiteral("program")).toString();
|
||||
const QStringList args = spec.value(QStringLiteral("args")).toStringList();
|
||||
QStringList args = unwrapStringList(spec.value(QStringLiteral("args")));
|
||||
QString prefixPath = spec.value(QStringLiteral("prefixPath")).toString();
|
||||
if (prefixPath.isEmpty() && profile.contains(QStringLiteral("prefixPath"))) {
|
||||
prefixPath = profile.value(QStringLiteral("prefixPath")).toString();
|
||||
}
|
||||
|
||||
if (program.isEmpty()) {
|
||||
out.insert(QStringLiteral("error"), QStringLiteral("missing program"));
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap effectiveEnv = spec.value(QStringLiteral("envOverrides")).toMap();
|
||||
{
|
||||
const QStringList rawProfileExtraArgs = unwrapStringList(profile.value(QStringLiteral("extraArgs")));
|
||||
for (const QString &a : rawProfileExtraArgs) {
|
||||
const QString trimmed = a.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
args.append(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
const QStringList rawSpecExtraArgs = unwrapStringList(spec.value(QStringLiteral("extraArgs")));
|
||||
for (const QString &a : rawSpecExtraArgs) {
|
||||
const QString trimmed = a.trimmed();
|
||||
if (!trimmed.isEmpty()) {
|
||||
args.append(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap effectiveEnv;
|
||||
if (profile.contains(QStringLiteral("envOverrides"))) {
|
||||
effectiveEnv = profile.value(QStringLiteral("envOverrides")).toMap();
|
||||
}
|
||||
const QVariantMap specEnv = unwrapVariantMap(spec.value(QStringLiteral("envOverrides")));
|
||||
for (auto it = specEnv.constBegin(); it != specEnv.constEnd(); ++it) {
|
||||
effectiveEnv.insert(it.key(), it.value());
|
||||
}
|
||||
|
||||
QVariantMap dllOverrides;
|
||||
if (profile.contains(QStringLiteral("dllOverrides"))) {
|
||||
dllOverrides = profile.value(QStringLiteral("dllOverrides")).toMap();
|
||||
}
|
||||
const QVariantMap specDllOverrides = unwrapVariantMap(spec.value(QStringLiteral("dllOverrides")));
|
||||
for (auto it = specDllOverrides.constBegin(); it != specDllOverrides.constEnd(); ++it) {
|
||||
const QString key = it.key().trimmed();
|
||||
if (key.isEmpty() || key.contains(QLatin1Char('=')) || key.contains(QLatin1Char(';'))) {
|
||||
continue;
|
||||
}
|
||||
const QString value = it.value().toString().trimmed();
|
||||
if (value.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
dllOverrides.insert(key, value);
|
||||
}
|
||||
|
||||
if (!dllOverrides.isEmpty() && !effectiveEnv.contains(QStringLiteral("WINEDLLOVERRIDES"))
|
||||
&& ((runner == QLatin1String("wine")) || (runner == QLatin1String("proton")))) {
|
||||
QStringList keys;
|
||||
keys.reserve(dllOverrides.size());
|
||||
for (auto it = dllOverrides.constBegin(); it != dllOverrides.constEnd(); ++it) {
|
||||
keys.push_back(it.key());
|
||||
}
|
||||
std::sort(keys.begin(), keys.end());
|
||||
|
||||
QStringList parts;
|
||||
parts.reserve(keys.size());
|
||||
for (const QString &k : keys) {
|
||||
const QString v = dllOverrides.value(k).toString().trimmed();
|
||||
if (v.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
parts.push_back(k + QLatin1Char('=') + v);
|
||||
}
|
||||
if (!parts.isEmpty()) {
|
||||
effectiveEnv.insert(QStringLiteral("WINEDLLOVERRIDES"), parts.join(QStringLiteral(";")));
|
||||
}
|
||||
}
|
||||
|
||||
if ((runner == QLatin1String("wine")) || (runner == QLatin1String("proton"))) {
|
||||
if (prefixPath.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -42,11 +42,18 @@ public Q_SLOTS:
|
|||
QVariantMap DeletePrefix(const QVariantMap &spec);
|
||||
QVariantMap ResolveLaunch(const QVariantMap &spec) const;
|
||||
|
||||
QVariantMap GetGameProfile(const QString &gameId) const;
|
||||
QVariantMap SetGameProfile(const QVariantMap &spec);
|
||||
QVariantMap ClearGameProfile(const QString &gameId);
|
||||
QVariantList ListGameProfiles() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void InstallStarted(const QString &installId, const QVariantMap &spec);
|
||||
void InstallProgress(const QString &installId, qint64 receivedBytes, qint64 totalBytes);
|
||||
void InstallFinished(const QString &installId, const QVariantMap &result);
|
||||
|
||||
void GameProfilesChanged();
|
||||
|
||||
private:
|
||||
struct RunnerInfo {
|
||||
QString id;
|
||||
|
|
@ -65,6 +72,11 @@ private:
|
|||
void loadRegistry();
|
||||
bool saveRegistry() const;
|
||||
|
||||
QString gameProfilesPath() const;
|
||||
void loadGameProfiles();
|
||||
bool saveGameProfiles() const;
|
||||
QVariantMap gameProfileForGameId(const QString &gameId) const;
|
||||
|
||||
QList<RunnerInfo> discoverRunners() const;
|
||||
QString defaultProtonExecutable() const;
|
||||
|
||||
|
|
@ -81,4 +93,6 @@ private:
|
|||
|
||||
QHash<QString, QPointer<QNetworkReply>> m_installReplies;
|
||||
QHash<QString, QPointer<QProcess>> m_installProcesses;
|
||||
|
||||
QHash<QString, QVariantMap> m_gameProfiles;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <QDBusPendingCall>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusReply>
|
||||
#include <QDBusVariant>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
|
@ -26,6 +27,52 @@ static const QString kRunnerService = QStringLiteral("org.kde.ALaKarte.Runner1")
|
|||
static const QString kRunnerPath = QStringLiteral("/org/kde/ALaKarte/Runner1");
|
||||
static const QString kRunnerInterface = QStringLiteral("org.kde.ALaKarte.Runner1");
|
||||
|
||||
namespace
|
||||
{
|
||||
static QVariant unwrapDbusVariant(QVariant v)
|
||||
{
|
||||
if (v.canConvert<QDBusVariant>()) {
|
||||
v = v.value<QDBusVariant>().variant();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static QVariantMap unwrapVariantMap(QVariant v)
|
||||
{
|
||||
v = unwrapDbusVariant(v);
|
||||
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||
return qdbus_cast<QVariantMap>(arg);
|
||||
}
|
||||
if (v.canConvert<QVariantMap>()) {
|
||||
return v.toMap();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static QVariantMap normalizeMap(QVariantMap m)
|
||||
{
|
||||
for (auto it = m.begin(); it != m.end(); ++it) {
|
||||
it.value() = unwrapDbusVariant(it.value());
|
||||
if (it.key() == QLatin1String("envOverrides")) {
|
||||
it.value() = unwrapVariantMap(it.value());
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static QVariantMap normalizeResultWithProfile(QVariantMap result)
|
||||
{
|
||||
result = normalizeMap(result);
|
||||
if (result.contains(QStringLiteral("profile"))) {
|
||||
QVariantMap profile = unwrapVariantMap(result.value(QStringLiteral("profile")));
|
||||
profile = normalizeMap(profile);
|
||||
result.insert(QStringLiteral("profile"), profile);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static QString stableIdForUrl(const QString &type, const QString &url)
|
||||
{
|
||||
const QByteArray digest = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Sha1).toHex();
|
||||
|
|
@ -80,6 +127,8 @@ RunnerManagerClient::RunnerManagerClient(QObject *parent)
|
|||
SLOT(onInstallProgress(QString, qlonglong, qlonglong)));
|
||||
|
||||
bus.connect(kRunnerService, kRunnerPath, kRunnerInterface, QStringLiteral("InstallFinished"), this, SLOT(onInstallFinished(QString, QVariantMap)));
|
||||
|
||||
bus.connect(kRunnerService, kRunnerPath, kRunnerInterface, QStringLiteral("GameProfilesChanged"), this, SLOT(onGameProfilesChanged()));
|
||||
}
|
||||
|
||||
if (QCoreApplication::instance()) {
|
||||
|
|
@ -89,6 +138,7 @@ RunnerManagerClient::RunnerManagerClient(QObject *parent)
|
|||
}
|
||||
|
||||
refreshRunners();
|
||||
refreshGameProfiles();
|
||||
}
|
||||
|
||||
RunnerManagerClient::~RunnerManagerClient()
|
||||
|
|
@ -213,6 +263,11 @@ QVariantList RunnerManagerClient::runners() const
|
|||
return m_runners;
|
||||
}
|
||||
|
||||
QVariantList RunnerManagerClient::gameProfiles() const
|
||||
{
|
||||
return m_gameProfiles;
|
||||
}
|
||||
|
||||
static QString normalizeHex(QString s)
|
||||
{
|
||||
s = s.trimmed().toLower();
|
||||
|
|
@ -417,6 +472,11 @@ void RunnerManagerClient::onInstallFinished(const QString &installId, const QVar
|
|||
refreshRunners();
|
||||
}
|
||||
|
||||
void RunnerManagerClient::onGameProfilesChanged()
|
||||
{
|
||||
refreshGameProfiles();
|
||||
}
|
||||
|
||||
void RunnerManagerClient::refreshRunners()
|
||||
{
|
||||
ensureRunnerDaemon();
|
||||
|
|
@ -472,6 +532,179 @@ void RunnerManagerClient::refreshRunners()
|
|||
});
|
||||
}
|
||||
|
||||
void RunnerManagerClient::refreshGameProfiles()
|
||||
{
|
||||
ensureRunnerDaemon();
|
||||
|
||||
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, QDBusConnection::sessionBus());
|
||||
QDBusPendingCall call = iface.asyncCall(QStringLiteral("ListGameProfiles"));
|
||||
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
|
||||
QDBusPendingReply<QVariantList> reply = *w;
|
||||
w->deleteLater();
|
||||
if (reply.isError()) {
|
||||
if (!m_refreshProfilesRetryPending && (reply.error().type() == QDBusError::ServiceUnknown || reply.error().type() == QDBusError::NoReply)) {
|
||||
m_refreshProfilesRetryPending = true;
|
||||
ensureRunnerDaemon();
|
||||
QTimer::singleShot(500, this, [this]() {
|
||||
m_refreshProfilesRetryPending = false;
|
||||
refreshGameProfiles();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const QVariantList raw = reply.value();
|
||||
|
||||
QVariantList list;
|
||||
list.reserve(raw.size());
|
||||
for (const QVariant &v : raw) {
|
||||
QVariantMap map;
|
||||
if (v.metaType() == QMetaType::fromType<QVariantMap>()) {
|
||||
map = v.toMap();
|
||||
} else if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
|
||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||
map = qdbus_cast<QVariantMap>(arg);
|
||||
} else if (v.canConvert<QVariantMap>()) {
|
||||
map = v.toMap();
|
||||
}
|
||||
list.push_back(normalizeMap(map));
|
||||
}
|
||||
|
||||
if (m_gameProfiles == list) {
|
||||
return;
|
||||
}
|
||||
m_gameProfiles = list;
|
||||
Q_EMIT gameProfilesChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void RunnerManagerClient::requestGameProfile(const QString &gameId)
|
||||
{
|
||||
const QString id = gameId.trimmed();
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureRunnerDaemon();
|
||||
|
||||
auto attempts = QSharedPointer<int>::create(0);
|
||||
auto callPtr = QSharedPointer<std::function<void()>>::create();
|
||||
*callPtr = [this, id, attempts, callPtr]() {
|
||||
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, QDBusConnection::sessionBus());
|
||||
QDBusPendingCall call = iface.asyncCall(QStringLiteral("GetGameProfile"), id);
|
||||
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
|
||||
QDBusPendingReply<QVariantMap> reply = *w;
|
||||
w->deleteLater();
|
||||
if (reply.isError()) {
|
||||
const QDBusError::ErrorType t = reply.error().type();
|
||||
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
|
||||
(*attempts)++;
|
||||
ensureRunnerDaemon();
|
||||
QTimer::singleShot(500, this, [callPtr]() {
|
||||
(*callPtr)();
|
||||
});
|
||||
return;
|
||||
}
|
||||
QVariantMap err;
|
||||
err.insert(QStringLiteral("ok"), false);
|
||||
err.insert(QStringLiteral("error"), reply.error().message());
|
||||
Q_EMIT gameProfileFetched(id, err);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap result = normalizeResultWithProfile(reply.value());
|
||||
Q_EMIT gameProfileFetched(id, result);
|
||||
});
|
||||
};
|
||||
(*callPtr)();
|
||||
}
|
||||
|
||||
void RunnerManagerClient::setGameProfile(const QVariantMap &spec)
|
||||
{
|
||||
const QString id = spec.value(QStringLiteral("gameId")).toString().trimmed();
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureRunnerDaemon();
|
||||
|
||||
auto attempts = QSharedPointer<int>::create(0);
|
||||
auto callPtr = QSharedPointer<std::function<void()>>::create();
|
||||
*callPtr = [this, spec, id, attempts, callPtr]() {
|
||||
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, QDBusConnection::sessionBus());
|
||||
QDBusPendingCall call = iface.asyncCall(QStringLiteral("SetGameProfile"), spec);
|
||||
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
|
||||
QDBusPendingReply<QVariantMap> reply = *w;
|
||||
w->deleteLater();
|
||||
if (reply.isError()) {
|
||||
const QDBusError::ErrorType t = reply.error().type();
|
||||
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
|
||||
(*attempts)++;
|
||||
ensureRunnerDaemon();
|
||||
QTimer::singleShot(500, this, [callPtr]() {
|
||||
(*callPtr)();
|
||||
});
|
||||
return;
|
||||
}
|
||||
QVariantMap err;
|
||||
err.insert(QStringLiteral("ok"), false);
|
||||
err.insert(QStringLiteral("error"), reply.error().message());
|
||||
Q_EMIT gameProfileSaved(id, err);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap result = normalizeResultWithProfile(reply.value());
|
||||
Q_EMIT gameProfileSaved(id, result);
|
||||
});
|
||||
};
|
||||
(*callPtr)();
|
||||
}
|
||||
|
||||
void RunnerManagerClient::clearGameProfile(const QString &gameId)
|
||||
{
|
||||
const QString id = gameId.trimmed();
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureRunnerDaemon();
|
||||
|
||||
auto attempts = QSharedPointer<int>::create(0);
|
||||
auto callPtr = QSharedPointer<std::function<void()>>::create();
|
||||
*callPtr = [this, id, attempts, callPtr]() {
|
||||
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, QDBusConnection::sessionBus());
|
||||
QDBusPendingCall call = iface.asyncCall(QStringLiteral("ClearGameProfile"), id);
|
||||
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
|
||||
QDBusPendingReply<QVariantMap> reply = *w;
|
||||
w->deleteLater();
|
||||
if (reply.isError()) {
|
||||
const QDBusError::ErrorType t = reply.error().type();
|
||||
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
|
||||
(*attempts)++;
|
||||
ensureRunnerDaemon();
|
||||
QTimer::singleShot(500, this, [callPtr]() {
|
||||
(*callPtr)();
|
||||
});
|
||||
return;
|
||||
}
|
||||
QVariantMap err;
|
||||
err.insert(QStringLiteral("ok"), false);
|
||||
err.insert(QStringLiteral("error"), reply.error().message());
|
||||
Q_EMIT gameProfileCleared(id, err);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap result = normalizeMap(reply.value());
|
||||
Q_EMIT gameProfileCleared(id, result);
|
||||
});
|
||||
};
|
||||
(*callPtr)();
|
||||
}
|
||||
|
||||
void RunnerManagerClient::uninstallRunner(const QString &runnerId)
|
||||
{
|
||||
const QString id = runnerId.trimmed();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class RunnerManagerClient : public QObject
|
|||
Q_PROPERTY(QString status READ status NOTIFY statusChanged)
|
||||
Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged)
|
||||
Q_PROPERTY(QVariantList runners READ runners NOTIFY runnersChanged)
|
||||
Q_PROPERTY(QVariantList gameProfiles READ gameProfiles NOTIFY gameProfilesChanged)
|
||||
|
||||
public:
|
||||
explicit RunnerManagerClient(QObject *parent = nullptr);
|
||||
|
|
@ -29,6 +30,7 @@ public:
|
|||
QString status() const;
|
||||
QString lastError() const;
|
||||
QVariantList runners() const;
|
||||
QVariantList gameProfiles() const;
|
||||
|
||||
Q_INVOKABLE void installRunnerFromUrl(const QString &url,
|
||||
const QString &sha256 = QString(),
|
||||
|
|
@ -41,6 +43,11 @@ public:
|
|||
Q_INVOKABLE void refreshRunners();
|
||||
Q_INVOKABLE void uninstallRunner(const QString &runnerId);
|
||||
|
||||
Q_INVOKABLE void refreshGameProfiles();
|
||||
Q_INVOKABLE void requestGameProfile(const QString &gameId);
|
||||
Q_INVOKABLE void setGameProfile(const QVariantMap &spec);
|
||||
Q_INVOKABLE void clearGameProfile(const QString &gameId);
|
||||
|
||||
Q_INVOKABLE void ensurePrefix(const QString &gameId, const QString &runner, const QString &prefixPath = QString());
|
||||
|
||||
Q_INVOKABLE void deletePrefix(const QString &gameId, const QString &prefixPath = QString());
|
||||
|
|
@ -52,14 +59,21 @@ Q_SIGNALS:
|
|||
void statusChanged();
|
||||
void lastErrorChanged();
|
||||
void runnersChanged();
|
||||
void gameProfilesChanged();
|
||||
void prefixEnsured(const QString &gameId, const QString &prefixPath);
|
||||
void prefixDeleted(const QString &gameId, const QString &prefixPath);
|
||||
|
||||
void gameProfileFetched(const QString &gameId, const QVariantMap &result);
|
||||
void gameProfileSaved(const QString &gameId, const QVariantMap &result);
|
||||
void gameProfileCleared(const QString &gameId, const QVariantMap &result);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onInstallStarted(const QString &installId, const QVariantMap &spec);
|
||||
void onInstallProgress(const QString &installId, qlonglong received, qlonglong total);
|
||||
void onInstallFinished(const QString &installId, const QVariantMap &result);
|
||||
|
||||
void onGameProfilesChanged();
|
||||
|
||||
private:
|
||||
void ensureRunnerDaemon();
|
||||
void shutdownSpawnedRunnerDaemon();
|
||||
|
|
@ -77,8 +91,10 @@ private:
|
|||
QString m_lastError;
|
||||
|
||||
QVariantList m_runners;
|
||||
QVariantList m_gameProfiles;
|
||||
|
||||
bool m_refreshRetryPending = false;
|
||||
bool m_refreshProfilesRetryPending = false;
|
||||
|
||||
class QProcess *m_runnerdProcess = nullptr;
|
||||
bool m_runnerdSpawnAttempted = false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue