mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-02-09 21:13:08 +00:00
GameCenter: integrate Runner1 resolution and improve daemon launch
This commit is contained in:
parent
97d915abc6
commit
4573a3106e
6 changed files with 885 additions and 69 deletions
|
|
@ -7,9 +7,13 @@
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QDBusConnectionInterface>
|
#include <QDBusConnectionInterface>
|
||||||
#include <QDBusError>
|
#include <QDBusError>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusMessage>
|
||||||
#include <QDBusObjectPath>
|
#include <QDBusObjectPath>
|
||||||
#include <QDBusReply>
|
#include <QDBusReply>
|
||||||
#include <QDBusVariant>
|
#include <QDBusVariant>
|
||||||
|
#include <QDBusVirtualObject>
|
||||||
|
#include <QDir>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
@ -19,8 +23,14 @@
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
static QString ensureScopeUnitName(const QString &unitName)
|
static QString ensureScopeUnitName(const QString &unitName)
|
||||||
{
|
{
|
||||||
if (unitName.endsWith(QLatin1String(".scope"))) {
|
if (unitName.endsWith(QLatin1String(".scope"))) {
|
||||||
|
|
@ -49,6 +59,10 @@ static QVariant normalizeForDbus(QVariant v)
|
||||||
return QString(v.toChar());
|
return QString(v.toChar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (v.userType() == QMetaType::QString || v.userType() == QMetaType::QStringList) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
if (v.canConvert<QDBusArgument>()) {
|
if (v.canConvert<QDBusArgument>()) {
|
||||||
const QDBusArgument arg = v.value<QDBusArgument>();
|
const QDBusArgument arg = v.value<QDBusArgument>();
|
||||||
const QMap<QString, QString> asStringMap = qdbus_cast<QMap<QString, QString>>(arg);
|
const QMap<QString, QString> asStringMap = qdbus_cast<QMap<QString, QString>>(arg);
|
||||||
|
|
@ -77,7 +91,7 @@ static QVariant normalizeForDbus(QVariant v)
|
||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
if (v.canConvert<QVariantList>()) {
|
if (v.userType() == QMetaType::QVariantList) {
|
||||||
QVariantList list = v.toList();
|
QVariantList list = v.toList();
|
||||||
for (QVariant &item : list) {
|
for (QVariant &item : list) {
|
||||||
item = normalizeForDbus(item);
|
item = normalizeForDbus(item);
|
||||||
|
|
@ -96,6 +110,57 @@ static QVariantMap normalizeVariantMapForDbus(const QVariantMap &map)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool tryResolveWithRunnerManager(const QVariantMap &spec, QVariantMap &out)
|
||||||
|
{
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected() || !bus.interface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus.interface()->isServiceRegistered(kRunnerService)) {
|
||||||
|
bus.interface()->startService(kRunnerService);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (!iface.isValid()) {
|
||||||
|
if (bus.interface()->startService(kRunnerService).isValid()) {
|
||||||
|
QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (retryIface.isValid()) {
|
||||||
|
retryIface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (retryReply.isValid()) {
|
||||||
|
out = normalizeVariantMapForDbus(retryReply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
iface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (!reply.isValid()) {
|
||||||
|
if (reply.error().type() == QDBusError::ServiceUnknown) {
|
||||||
|
bus.interface()->startService(kRunnerService);
|
||||||
|
QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (!retryIface.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
retryIface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (!retryReply.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = normalizeVariantMapForDbus(retryReply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = normalizeVariantMapForDbus(reply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static QString mapStringValue(const QVariantMap &map, const QStringList &keys)
|
static QString mapStringValue(const QVariantMap &map, const QStringList &keys)
|
||||||
{
|
{
|
||||||
for (const QString &k : keys) {
|
for (const QString &k : keys) {
|
||||||
|
|
@ -171,6 +236,445 @@ private:
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
static const QString kGameCenterService = QStringLiteral("org.kde.GameCenter1");
|
||||||
|
static const QString kGameCenterPath = QStringLiteral("/org/kde/GameCenter1");
|
||||||
|
static const QString kGameCenterInterface = QStringLiteral("org.kde.GameCenter1");
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
static QString gameCenterIntrospectionXml()
|
||||||
|
{
|
||||||
|
return QStringLiteral(
|
||||||
|
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-Bus Object Introspection 1.0//EN\" "
|
||||||
|
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
|
||||||
|
"<node>"
|
||||||
|
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
|
||||||
|
" <method name=\"Introspect\">"
|
||||||
|
" <arg name=\"xml\" type=\"s\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" </interface>"
|
||||||
|
" <interface name=\"org.kde.GameCenter1\">"
|
||||||
|
" <method name=\"Ping\">"
|
||||||
|
" <arg name=\"out\" type=\"s\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"Version\">"
|
||||||
|
" <arg name=\"major\" type=\"u\" direction=\"out\"/>"
|
||||||
|
" <arg name=\"minor\" type=\"u\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"GetCapabilities\">"
|
||||||
|
" <arg name=\"caps\" type=\"a{sv}\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"ListSessions\">"
|
||||||
|
" <arg name=\"sessions\" type=\"av\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"GetSession\">"
|
||||||
|
" <arg name=\"sessionId\" type=\"s\" direction=\"in\"/>"
|
||||||
|
" <arg name=\"session\" type=\"a{sv}\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"Launch\">"
|
||||||
|
" <arg name=\"launchSpec\" type=\"a{sv}\" direction=\"in\"/>"
|
||||||
|
" <arg name=\"sessionId\" type=\"s\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"Stop\">"
|
||||||
|
" <arg name=\"sessionId\" type=\"s\" direction=\"in\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"StopByGameId\">"
|
||||||
|
" <arg name=\"gameId\" type=\"s\" direction=\"in\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"SetPolicy\">"
|
||||||
|
" <arg name=\"policy\" type=\"a{sv}\" direction=\"in\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <method name=\"GetPolicy\">"
|
||||||
|
" <arg name=\"policy\" type=\"a{sv}\" direction=\"out\"/>"
|
||||||
|
" </method>"
|
||||||
|
" <signal name=\"SessionAdded\">"
|
||||||
|
" <arg name=\"session\" type=\"a{sv}\"/>"
|
||||||
|
" </signal>"
|
||||||
|
" <signal name=\"SessionChanged\">"
|
||||||
|
" <arg name=\"session\" type=\"a{sv}\"/>"
|
||||||
|
" </signal>"
|
||||||
|
" <signal name=\"SessionRemoved\">"
|
||||||
|
" <arg name=\"sessionId\" type=\"s\"/>"
|
||||||
|
" <arg name=\"finalState\" type=\"a{sv}\"/>"
|
||||||
|
" </signal>"
|
||||||
|
" <signal name=\"LaunchFailed\">"
|
||||||
|
" <arg name=\"error\" type=\"a{sv}\"/>"
|
||||||
|
" </signal>"
|
||||||
|
" </interface>"
|
||||||
|
"</node>");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class GameCenterSystemProxyPrivate;
|
||||||
|
|
||||||
|
class GameCenterProxyVirtualObject : public QDBusVirtualObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GameCenterProxyVirtualObject(GameCenterSystemProxyPrivate *proxy, QObject *parent = nullptr)
|
||||||
|
: QDBusVirtualObject(parent)
|
||||||
|
, m_proxy(proxy)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString introspect(const QString &path) const override;
|
||||||
|
bool handleMessage(const QDBusMessage &message, const QDBusConnection &connection) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GameCenterSystemProxyPrivate *m_proxy = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BackendSignalForwarder : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BackendSignalForwarder(uint uid, GameCenterSystemProxyPrivate *proxy, QObject *parent = nullptr)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_uid(uid)
|
||||||
|
, m_proxy(proxy)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void onSessionAdded(const QVariantMap &session);
|
||||||
|
void onSessionChanged(const QVariantMap &session);
|
||||||
|
void onSessionRemoved(const QString &sessionId, const QVariantMap &finalState);
|
||||||
|
void onLaunchFailed(const QVariantMap &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint m_uid = 0;
|
||||||
|
GameCenterSystemProxyPrivate *m_proxy = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BackendBusEntry {
|
||||||
|
BackendBusEntry(const QDBusConnection &bus, BackendSignalForwarder *forwarder)
|
||||||
|
: bus(bus)
|
||||||
|
, forwarder(forwarder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection bus;
|
||||||
|
BackendSignalForwarder *forwarder = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GameCenterSystemProxyPrivate : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GameCenterSystemProxyPrivate(QObject *parent = nullptr)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
bool handleSystemMessage(const QDBusMessage &message, const QDBusConnection &connection);
|
||||||
|
void forwardSignalToUid(uint uid, const QString &member, const QList<QVariant> &args);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool uidForSystemSender(const QString &senderUniqueName, uint &uid) const;
|
||||||
|
void rememberClient(const QString &senderUniqueName, uint uid);
|
||||||
|
void forgetClient(const QString &senderUniqueName);
|
||||||
|
BackendBusEntry *ensureBackend(uint uid);
|
||||||
|
|
||||||
|
QDBusConnection m_systemBus = QDBusConnection::systemBus();
|
||||||
|
std::unique_ptr<GameCenterProxyVirtualObject> m_virtualObject;
|
||||||
|
|
||||||
|
QHash<uint, BackendBusEntry> m_backends;
|
||||||
|
QHash<QString, uint> m_clientUid;
|
||||||
|
QHash<uint, QSet<QString>> m_uidClients;
|
||||||
|
};
|
||||||
|
|
||||||
|
QString GameCenterProxyVirtualObject::introspect(const QString &path) const
|
||||||
|
{
|
||||||
|
if (path == kGameCenterPath) {
|
||||||
|
return gameCenterIntrospectionXml();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameCenterProxyVirtualObject::handleMessage(const QDBusMessage &message, const QDBusConnection &connection)
|
||||||
|
{
|
||||||
|
if (!m_proxy) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_proxy->handleSystemMessage(message, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendSignalForwarder::onSessionAdded(const QVariantMap &session)
|
||||||
|
{
|
||||||
|
if (m_proxy) {
|
||||||
|
m_proxy->forwardSignalToUid(m_uid, QStringLiteral("SessionAdded"), {session});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendSignalForwarder::onSessionChanged(const QVariantMap &session)
|
||||||
|
{
|
||||||
|
if (m_proxy) {
|
||||||
|
m_proxy->forwardSignalToUid(m_uid, QStringLiteral("SessionChanged"), {session});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendSignalForwarder::onSessionRemoved(const QString &sessionId, const QVariantMap &finalState)
|
||||||
|
{
|
||||||
|
if (m_proxy) {
|
||||||
|
m_proxy->forwardSignalToUid(m_uid, QStringLiteral("SessionRemoved"), {sessionId, finalState});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BackendSignalForwarder::onLaunchFailed(const QVariantMap &error)
|
||||||
|
{
|
||||||
|
if (m_proxy) {
|
||||||
|
m_proxy->forwardSignalToUid(m_uid, QStringLiteral("LaunchFailed"), {error});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameCenterSystemProxyPrivate::init()
|
||||||
|
{
|
||||||
|
if (!m_systemBus.isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_systemBus.registerService(kGameCenterService)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_virtualObject = std::make_unique<GameCenterProxyVirtualObject>(this);
|
||||||
|
if (!m_systemBus.registerVirtualObject(kGameCenterPath, m_virtualObject.get(), QDBusConnection::SingleNode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_systemBus.interface()) {
|
||||||
|
connect(m_systemBus.interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &GameCenterSystemProxyPrivate::handleServiceOwnerChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameCenterSystemProxyPrivate::uidForSystemSender(const QString &senderUniqueName, uint &uid) const
|
||||||
|
{
|
||||||
|
if (!m_systemBus.interface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusReply<uint> uidReply = m_systemBus.interface()->serviceUid(senderUniqueName);
|
||||||
|
if (!uidReply.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uid = uidReply.value();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameCenterSystemProxyPrivate::rememberClient(const QString &senderUniqueName, uint uid)
|
||||||
|
{
|
||||||
|
const auto it = m_clientUid.constFind(senderUniqueName);
|
||||||
|
if (it != m_clientUid.constEnd()) {
|
||||||
|
if (it.value() == uid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
forgetClient(senderUniqueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_clientUid.insert(senderUniqueName, uid);
|
||||||
|
m_uidClients[uid].insert(senderUniqueName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameCenterSystemProxyPrivate::forgetClient(const QString &senderUniqueName)
|
||||||
|
{
|
||||||
|
const auto it = m_clientUid.find(senderUniqueName);
|
||||||
|
if (it == m_clientUid.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint uid = it.value();
|
||||||
|
m_clientUid.erase(it);
|
||||||
|
|
||||||
|
auto clientsIt = m_uidClients.find(uid);
|
||||||
|
if (clientsIt != m_uidClients.end()) {
|
||||||
|
clientsIt.value().remove(senderUniqueName);
|
||||||
|
if (clientsIt.value().isEmpty()) {
|
||||||
|
m_uidClients.erase(clientsIt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameCenterSystemProxyPrivate::handleServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
|
||||||
|
{
|
||||||
|
Q_UNUSED(oldOwner)
|
||||||
|
|
||||||
|
if (!newOwner.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.startsWith(QLatin1Char(':'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forgetClient(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendBusEntry *GameCenterSystemProxyPrivate::ensureBackend(uint uid)
|
||||||
|
{
|
||||||
|
const auto it = m_backends.find(uid);
|
||||||
|
if (it != m_backends.end()) {
|
||||||
|
return &it.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString address = QStringLiteral("unix:path=/run/user/%1/bus").arg(uid);
|
||||||
|
const QString connectionName = QStringLiteral("gamecenter-user-%1").arg(uid);
|
||||||
|
QDBusConnection backend = QDBusConnection::connectToBus(address, connectionName);
|
||||||
|
if (!backend.isConnected()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *forwarder = new BackendSignalForwarder(uid, this, this);
|
||||||
|
BackendBusEntry entry(backend, forwarder);
|
||||||
|
|
||||||
|
backend.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionAdded"), forwarder, SLOT(onSessionAdded(QVariantMap)));
|
||||||
|
|
||||||
|
backend
|
||||||
|
.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionChanged"), forwarder, SLOT(onSessionChanged(QVariantMap)));
|
||||||
|
|
||||||
|
backend.connect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionRemoved"),
|
||||||
|
forwarder,
|
||||||
|
SLOT(onSessionRemoved(QString, QVariantMap)));
|
||||||
|
|
||||||
|
backend.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("LaunchFailed"), forwarder, SLOT(onLaunchFailed(QVariantMap)));
|
||||||
|
|
||||||
|
auto inserted = m_backends.insert(uid, entry);
|
||||||
|
return &inserted.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameCenterSystemProxyPrivate::forwardSignalToUid(uint uid, const QString &member, const QList<QVariant> &args)
|
||||||
|
{
|
||||||
|
if (!m_systemBus.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto clientsIt = m_uidClients.constFind(uid);
|
||||||
|
if (clientsIt == m_uidClients.constEnd()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSet<QString> clients = clientsIt.value();
|
||||||
|
for (const QString &client : clients) {
|
||||||
|
if (client.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage signal = QDBusMessage::createTargetedSignal(client, kGameCenterPath, kGameCenterInterface, member);
|
||||||
|
signal.setArguments(args);
|
||||||
|
m_systemBus.send(signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameCenterSystemProxyPrivate::handleSystemMessage(const QDBusMessage &message, const QDBusConnection &connection)
|
||||||
|
{
|
||||||
|
if (message.type() != QDBusMessage::MethodCallMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.interface() == QLatin1String("org.freedesktop.DBus.Introspectable") && message.member() == QLatin1String("Introspect")) {
|
||||||
|
if (!message.isReplyRequired()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
connection.send(message.createReply(gameCenterIntrospectionXml()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString sender = message.service();
|
||||||
|
if (sender.isEmpty()) {
|
||||||
|
if (message.isReplyRequired()) {
|
||||||
|
connection.send(message.createErrorReply(QDBusError::AccessDenied, QStringLiteral("missing sender")));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint uid = 0;
|
||||||
|
if (!uidForSystemSender(sender, uid)) {
|
||||||
|
if (message.isReplyRequired()) {
|
||||||
|
connection.send(message.createErrorReply(QDBusError::AccessDenied, QStringLiteral("failed to determine caller uid")));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberClient(sender, uid);
|
||||||
|
|
||||||
|
BackendBusEntry *backend = ensureBackend(uid);
|
||||||
|
if (!backend) {
|
||||||
|
if (message.isReplyRequired()) {
|
||||||
|
connection.send(message.createErrorReply(QDBusError::ServiceUnknown, QStringLiteral("user bus backend unavailable")));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage forward = QDBusMessage::createMethodCall(kGameCenterService, message.path(), message.interface(), message.member());
|
||||||
|
forward.setArguments(message.arguments());
|
||||||
|
forward.setAutoStartService(true);
|
||||||
|
|
||||||
|
const QDBusMessage backendReply = backend->bus.call(forward, QDBus::Block, -1);
|
||||||
|
if (!message.isReplyRequired()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backendReply.type() == QDBusMessage::ErrorMessage) {
|
||||||
|
connection.send(message.createErrorReply(backendReply.errorName(), backendReply.errorMessage()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backendReply.type() != QDBusMessage::ReplyMessage) {
|
||||||
|
connection.send(message.createErrorReply(QDBusError::Failed, QStringLiteral("backend did not reply")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage out = message.createReply(backendReply.arguments());
|
||||||
|
if (message.interface() == kGameCenterInterface && message.member() == QLatin1String("GetCapabilities")) {
|
||||||
|
if (!out.arguments().isEmpty()) {
|
||||||
|
QVariant capsV = unwrapDbusVariant(out.arguments().first());
|
||||||
|
QVariantMap caps;
|
||||||
|
if (capsV.canConvert<QDBusArgument>()) {
|
||||||
|
const QDBusArgument arg = capsV.value<QDBusArgument>();
|
||||||
|
caps = qdbus_cast<QVariantMap>(arg);
|
||||||
|
} else if (capsV.canConvert<QVariantMap>()) {
|
||||||
|
caps = capsV.toMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!caps.isEmpty()) {
|
||||||
|
caps.insert(QStringLiteral("supportsSystemBus"), true);
|
||||||
|
out.setArguments({caps});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.send(out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameCenterSystemProxy::GameCenterSystemProxy(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, d(std::make_unique<GameCenterSystemProxyPrivate>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GameCenterSystemProxy::~GameCenterSystemProxy() = default;
|
||||||
|
|
||||||
|
bool GameCenterSystemProxy::init()
|
||||||
|
{
|
||||||
|
return d->init();
|
||||||
|
}
|
||||||
|
|
||||||
void GameCenterDaemon::watchSystemdUnit(const QString &sessionId, const QString &unitName, const QDBusObjectPath &unitPath)
|
void GameCenterDaemon::watchSystemdUnit(const QString &sessionId, const QString &unitName, const QDBusObjectPath &unitPath)
|
||||||
{
|
{
|
||||||
if (unitName.isEmpty()) {
|
if (unitName.isEmpty()) {
|
||||||
|
|
@ -545,6 +1049,69 @@ QString GameCenterDaemon::launchDirect(const QVariantMap &launchSpec)
|
||||||
envOverrides = extractVariantMap(launchSpec.value(QStringLiteral("envOverrides")));
|
envOverrides = extractVariantMap(launchSpec.value(QStringLiteral("envOverrides")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString runnerId = mapStringValue(launchSpec, {QStringLiteral("runnerId")});
|
||||||
|
const QString runner = mapStringValue(launchSpec, {QStringLiteral("runner")});
|
||||||
|
const QString runnerPath = mapStringValue(launchSpec, {QStringLiteral("runnerPath")});
|
||||||
|
const QString prefixPath = mapStringValue(launchSpec, {QStringLiteral("prefixPath")});
|
||||||
|
|
||||||
|
const QString requestedProgram = mapStringValue(launchSpec, {QStringLiteral("requestedProgram")});
|
||||||
|
QStringList requestedArgs;
|
||||||
|
if (launchSpec.contains(QStringLiteral("requestedArgs"))) {
|
||||||
|
requestedArgs = variantToStringList(launchSpec.value(QStringLiteral("requestedArgs")));
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap requestedEnvOverrides;
|
||||||
|
if (launchSpec.contains(QStringLiteral("requestedEnvOverrides"))) {
|
||||||
|
requestedEnvOverrides = extractVariantMap(launchSpec.value(QStringLiteral("requestedEnvOverrides")));
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool wantsRunnerResolution = !runnerId.isEmpty() || runner == QLatin1String("wine") || runner == QLatin1String("proton");
|
||||||
|
if (wantsRunnerResolution) {
|
||||||
|
const QString baseProgram = requestedProgram.isEmpty() ? program : requestedProgram;
|
||||||
|
const QStringList baseArgs = requestedArgs.isEmpty() ? args : requestedArgs;
|
||||||
|
const QVariantMap baseEnv = requestedEnvOverrides.isEmpty() ? envOverrides : requestedEnvOverrides;
|
||||||
|
|
||||||
|
const QVariantMap resolveSpec = {
|
||||||
|
{QStringLiteral("runnerId"), runnerId},
|
||||||
|
{QStringLiteral("runner"), runner},
|
||||||
|
{QStringLiteral("runnerPath"), runnerPath},
|
||||||
|
{QStringLiteral("gameId"), gameId},
|
||||||
|
{QStringLiteral("prefixPath"), prefixPath},
|
||||||
|
{QStringLiteral("program"), baseProgram},
|
||||||
|
{QStringLiteral("args"), baseArgs},
|
||||||
|
{QStringLiteral("envOverrides"), baseEnv},
|
||||||
|
};
|
||||||
|
|
||||||
|
QVariantMap resolved;
|
||||||
|
if (tryResolveWithRunnerManager(resolveSpec, resolved)) {
|
||||||
|
if (!resolved.value(QStringLiteral("ok")).toBool()) {
|
||||||
|
QVariantMap ctx = launchSpec;
|
||||||
|
ctx.insert(QStringLiteral("runnerResolution"), resolved);
|
||||||
|
failLaunch(resolved.value(QStringLiteral("error")).toString(), ctx);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
program = mapStringValue(resolved, {QStringLiteral("finalProgram")});
|
||||||
|
if (resolved.contains(QStringLiteral("finalArgs"))) {
|
||||||
|
args = variantToStringList(resolved.value(QStringLiteral("finalArgs")));
|
||||||
|
}
|
||||||
|
if (resolved.contains(QStringLiteral("effectiveEnv"))) {
|
||||||
|
envOverrides = extractVariantMap(resolved.value(QStringLiteral("effectiveEnv")));
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString resolvedPrefixPath = mapStringValue(resolved, {QStringLiteral("resolvedPrefixPath")});
|
||||||
|
if (!resolvedPrefixPath.isEmpty()) {
|
||||||
|
QDir().mkpath(resolvedPrefixPath);
|
||||||
|
}
|
||||||
|
} else if (!runnerId.isEmpty()) {
|
||||||
|
QVariantMap ctx = launchSpec;
|
||||||
|
ctx.insert(QStringLiteral("runnerResolution"),
|
||||||
|
QVariantMap{{QStringLiteral("ok"), false}, {QStringLiteral("error"), QStringLiteral("runner service unavailable")}});
|
||||||
|
failLaunch(QStringLiteral("runner service is not available"), ctx);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
const QString sessionId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
const QString unitName = ensureScopeUnitName(QStringLiteral("alakarte-game-%1").arg(sessionId));
|
const QString unitName = ensureScopeUnitName(QStringLiteral("alakarte-game-%1").arg(sessionId));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QDBusObjectPath>
|
#include <QDBusObjectPath>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
@ -94,3 +96,19 @@ private:
|
||||||
QHash<QString, QString> m_unitNameToSessionId;
|
QHash<QString, QString> m_unitNameToSessionId;
|
||||||
QHash<QString, QObject *> m_unitPathWatchers;
|
QHash<QString, QObject *> m_unitPathWatchers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GameCenterSystemProxyPrivate;
|
||||||
|
|
||||||
|
class GameCenterSystemProxy : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GameCenterSystemProxy(QObject *parent = nullptr);
|
||||||
|
~GameCenterSystemProxy() override;
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<GameCenterSystemProxyPrivate> d;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
|
||||||
|
|
||||||
|
#include <QCommandLineOption>
|
||||||
|
#include <QCommandLineParser>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include "gamecenterdaemon.h"
|
#include "gamecenterdaemon.h"
|
||||||
|
|
@ -9,6 +11,21 @@ int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription(QStringLiteral("A-La-Karte Game Center"));
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addOption(QCommandLineOption(QStringLiteral("system"), QStringLiteral("Run as system bus proxy")));
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
if (parser.isSet(QStringLiteral("system"))) {
|
||||||
|
GameCenterSystemProxy proxy;
|
||||||
|
if (!proxy.init()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
|
||||||
GameCenterDaemon daemon;
|
GameCenterDaemon daemon;
|
||||||
if (!daemon.init()) {
|
if (!daemon.init()) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDBusArgument>
|
#include <QDBusArgument>
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusConnectionInterface>
|
||||||
|
#include <QDBusError>
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
#include <QDBusReply>
|
#include <QDBusReply>
|
||||||
#include <QDBusVariant>
|
#include <QDBusVariant>
|
||||||
|
|
@ -19,16 +21,88 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
static QStringList steamCandidateRoots()
|
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 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");
|
||||||
|
|
||||||
|
static bool pingDaemon(QDBusConnection bus)
|
||||||
{
|
{
|
||||||
const QString home = QDir::homePath();
|
if (!bus.isConnected()) {
|
||||||
return {
|
return false;
|
||||||
home + QStringLiteral("/.steam/root"),
|
}
|
||||||
home + QStringLiteral("/.steam/steam"),
|
|
||||||
home + QStringLiteral("/.local/share/Steam"),
|
QDBusInterface iface(kGameCenterService, kGameCenterPath, kGameCenterInterface, bus);
|
||||||
home + QStringLiteral("/.var/app/com.valvesoftware.Steam/data/Steam"),
|
if (!iface.isValid()) {
|
||||||
home + QStringLiteral("/.var/app/com.valvesoftware.Steam/.local/share/Steam"),
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
iface.setTimeout(2000);
|
||||||
|
const QDBusReply<QString> reply = iface.call(QStringLiteral("Ping"));
|
||||||
|
return reply.isValid() && reply.value() == QLatin1String("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disconnectDaemonSignals(QDBusConnection bus, GameLauncher *launcher)
|
||||||
|
{
|
||||||
|
if (!bus.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.disconnect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionAdded"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonSessionAdded(QVariantMap)));
|
||||||
|
|
||||||
|
bus.disconnect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionChanged"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonSessionChanged(QVariantMap)));
|
||||||
|
|
||||||
|
bus.disconnect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionRemoved"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonSessionRemoved(QString, QVariantMap)));
|
||||||
|
|
||||||
|
bus.disconnect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("LaunchFailed"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonLaunchFailed(QVariantMap)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connectDaemonSignals(QDBusConnection bus, GameLauncher *launcher)
|
||||||
|
{
|
||||||
|
if (!bus.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("SessionAdded"), launcher, SLOT(onDaemonSessionAdded(QVariantMap)));
|
||||||
|
|
||||||
|
bus.connect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionChanged"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonSessionChanged(QVariantMap)));
|
||||||
|
|
||||||
|
bus.connect(kGameCenterService,
|
||||||
|
kGameCenterPath,
|
||||||
|
kGameCenterInterface,
|
||||||
|
QStringLiteral("SessionRemoved"),
|
||||||
|
launcher,
|
||||||
|
SLOT(onDaemonSessionRemoved(QString, QVariantMap)));
|
||||||
|
|
||||||
|
bus.connect(kGameCenterService, kGameCenterPath, kGameCenterInterface, QStringLiteral("LaunchFailed"), launcher, SLOT(onDaemonLaunchFailed(QVariantMap)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static QVariant unwrapDbusVariant(QVariant v)
|
static QVariant unwrapDbusVariant(QVariant v)
|
||||||
|
|
@ -48,6 +122,59 @@ static QVariantMap unwrapVariantMap(const QVariantMap &map)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool tryResolveWithRunnerManager(const QVariantMap &spec, QVariantMap &out)
|
||||||
|
{
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected() || !bus.interface()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus.interface()->isServiceRegistered(kRunnerService)) {
|
||||||
|
bus.interface()->startService(kRunnerService);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface iface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (!iface.isValid()) {
|
||||||
|
if (bus.interface()->startService(kRunnerService).isValid()) {
|
||||||
|
QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (retryIface.isValid()) {
|
||||||
|
retryIface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (retryReply.isValid()) {
|
||||||
|
out = unwrapVariantMap(retryReply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
iface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> reply = iface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (!reply.isValid()) {
|
||||||
|
if (reply.error().type() == QDBusError::ServiceUnknown) {
|
||||||
|
bus.interface()->startService(kRunnerService);
|
||||||
|
QDBusInterface retryIface(kRunnerService, kRunnerPath, kRunnerInterface, bus);
|
||||||
|
if (!retryIface.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
retryIface.setTimeout(2000);
|
||||||
|
const QDBusReply<QVariantMap> retryReply = retryIface.call(QStringLiteral("ResolveLaunch"), spec);
|
||||||
|
if (!retryReply.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = unwrapVariantMap(retryReply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = unwrapVariantMap(reply.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QStringList steamCandidateRoots();
|
||||||
|
|
||||||
static QString findSteamClientInstallPathFromProton(const QString &protonExe)
|
static QString findSteamClientInstallPathFromProton(const QString &protonExe)
|
||||||
{
|
{
|
||||||
if (protonExe.isEmpty()) {
|
if (protonExe.isEmpty()) {
|
||||||
|
|
@ -130,40 +257,23 @@ static QString discoverDefaultProtonExecutable()
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static QStringList steamCandidateRoots()
|
||||||
|
{
|
||||||
|
const QString home = QDir::homePath();
|
||||||
|
return {
|
||||||
|
home + QStringLiteral("/.steam/root"),
|
||||||
|
home + QStringLiteral("/.steam/steam"),
|
||||||
|
home + QStringLiteral("/.local/share/Steam"),
|
||||||
|
home + QStringLiteral("/.var/app/com.valvesoftware.Steam/data/Steam"),
|
||||||
|
home + QStringLiteral("/.var/app/com.valvesoftware.Steam/.local/share/Steam"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
GameLauncher::GameLauncher(QObject *parent)
|
GameLauncher::GameLauncher(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, m_daemonAvailable(false)
|
||||||
|
, m_usingSystemBus(false)
|
||||||
{
|
{
|
||||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
||||||
if (bus.isConnected()) {
|
|
||||||
bus.connect(QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("SessionAdded"),
|
|
||||||
this,
|
|
||||||
SLOT(onDaemonSessionAdded(QVariantMap)));
|
|
||||||
|
|
||||||
bus.connect(QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("SessionChanged"),
|
|
||||||
this,
|
|
||||||
SLOT(onDaemonSessionChanged(QVariantMap)));
|
|
||||||
|
|
||||||
bus.connect(QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("SessionRemoved"),
|
|
||||||
this,
|
|
||||||
SLOT(onDaemonSessionRemoved(QString, QVariantMap)));
|
|
||||||
|
|
||||||
bus.connect(QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("LaunchFailed"),
|
|
||||||
this,
|
|
||||||
SLOT(onDaemonLaunchFailed(QVariantMap)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto *app = qobject_cast<App *>(parent)) {
|
if (auto *app = qobject_cast<App *>(parent)) {
|
||||||
connect(app->gameModel(), &GameModel::countChanged, this, [this]() {
|
connect(app->gameModel(), &GameModel::countChanged, this, [this]() {
|
||||||
QTimer::singleShot(0, this, [this]() {
|
QTimer::singleShot(0, this, [this]() {
|
||||||
|
|
@ -222,12 +332,14 @@ QVariantMap GameLauncher::resolveLaunchInfo(Game *game) const
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString runner = game->launchRunner().trimmed();
|
const QString runner = game->launchRunner().trimmed();
|
||||||
|
const QString runnerId = game->launchRunnerId().trimmed();
|
||||||
const QString runnerPath = game->launchRunnerPath().trimmed();
|
const QString runnerPath = game->launchRunnerPath().trimmed();
|
||||||
const QString prefixPath = game->launchPrefixPath().trimmed();
|
const QString prefixPath = game->launchPrefixPath().trimmed();
|
||||||
const QVariantMap launchEnv = game->launchEnv();
|
const QVariantMap launchEnv = game->launchEnv();
|
||||||
const bool hasLaunchOverrides = !runner.isEmpty() || !runnerPath.isEmpty() || !prefixPath.isEmpty() || !launchEnv.isEmpty();
|
const bool hasLaunchOverrides = !runner.isEmpty() || !runnerId.isEmpty() || !runnerPath.isEmpty() || !prefixPath.isEmpty() || !launchEnv.isEmpty();
|
||||||
|
|
||||||
info.insert(QStringLiteral("runner"), runner);
|
info.insert(QStringLiteral("runner"), runner);
|
||||||
|
info.insert(QStringLiteral("runnerId"), runnerId);
|
||||||
info.insert(QStringLiteral("runnerPath"), runnerPath);
|
info.insert(QStringLiteral("runnerPath"), runnerPath);
|
||||||
info.insert(QStringLiteral("prefixPath"), prefixPath);
|
info.insert(QStringLiteral("prefixPath"), prefixPath);
|
||||||
info.insert(QStringLiteral("workingDirectory"), game->workingDirectory());
|
info.insert(QStringLiteral("workingDirectory"), game->workingDirectory());
|
||||||
|
|
@ -287,6 +399,47 @@ QVariantMap GameLauncher::resolveLaunchInfo(Game *game) const
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!runnerId.isEmpty() || runner == QLatin1String("wine") || runner == QLatin1String("proton")) {
|
||||||
|
QVariantMap runnerSpec = {
|
||||||
|
{QStringLiteral("runnerId"), runnerId},
|
||||||
|
{QStringLiteral("runner"), runner},
|
||||||
|
{QStringLiteral("runnerPath"), runnerPath},
|
||||||
|
{QStringLiteral("gameId"), game->id()},
|
||||||
|
{QStringLiteral("prefixPath"), prefixPath},
|
||||||
|
{QStringLiteral("program"), program},
|
||||||
|
{QStringLiteral("args"), parts},
|
||||||
|
{QStringLiteral("envOverrides"), envOverrides},
|
||||||
|
};
|
||||||
|
|
||||||
|
QVariantMap resolved;
|
||||||
|
if (tryResolveWithRunnerManager(runnerSpec, resolved)) {
|
||||||
|
if (!resolved.value(QStringLiteral("ok")).toBool()) {
|
||||||
|
info.insert(QStringLiteral("error"), resolved.value(QStringLiteral("error")).toString());
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.insert(QStringLiteral("finalProgram"), resolved.value(QStringLiteral("finalProgram")).toString());
|
||||||
|
info.insert(QStringLiteral("finalArgs"), resolved.value(QStringLiteral("finalArgs")).toStringList());
|
||||||
|
info.insert(QStringLiteral("effectiveEnv"), resolved.value(QStringLiteral("effectiveEnv")).toMap());
|
||||||
|
|
||||||
|
const QString resolvedPrefixPath = resolved.value(QStringLiteral("resolvedPrefixPath")).toString();
|
||||||
|
if (!resolvedPrefixPath.isEmpty()) {
|
||||||
|
info.insert(QStringLiteral("resolvedPrefixPath"), resolvedPrefixPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString resolvedSteamInstallPath = resolved.value(QStringLiteral("resolvedSteamInstallPath")).toString();
|
||||||
|
if (!resolvedSteamInstallPath.isEmpty()) {
|
||||||
|
info.insert(QStringLiteral("resolvedSteamInstallPath"), resolvedSteamInstallPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.insert(QStringLiteral("ok"), true);
|
||||||
|
return info;
|
||||||
|
} else if (!runnerId.isEmpty()) {
|
||||||
|
info.insert(QStringLiteral("error"), tr("Runner service is not available"));
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (runner == QLatin1String("custom")) {
|
if (runner == QLatin1String("custom")) {
|
||||||
if (runnerPath.isEmpty()) {
|
if (runnerPath.isEmpty()) {
|
||||||
info.insert(QStringLiteral("error"), tr("Custom runner is enabled but no runner path is configured"));
|
info.insert(QStringLiteral("error"), tr("Custom runner is enabled but no runner path is configured"));
|
||||||
|
|
@ -354,7 +507,6 @@ QVariantMap GameLauncher::resolveLaunchInfo(Game *game) const
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameLauncher::launchGame(Game *game)
|
void GameLauncher::launchGame(Game *game)
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!game) {
|
if (!game) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -376,7 +528,12 @@ void GameLauncher::launchGame(Game *game)
|
||||||
const QString launchType = info.value(QStringLiteral("launchType")).toString();
|
const QString launchType = info.value(QStringLiteral("launchType")).toString();
|
||||||
const QString provider = info.value(QStringLiteral("provider")).toString();
|
const QString provider = info.value(QStringLiteral("provider")).toString();
|
||||||
|
|
||||||
if (info.value(QStringLiteral("runner")).toString() == QLatin1String("proton")) {
|
const QString runner = info.value(QStringLiteral("runner")).toString();
|
||||||
|
const QString runnerId = info.value(QStringLiteral("runnerId")).toString();
|
||||||
|
const QString runnerPath = info.value(QStringLiteral("runnerPath")).toString();
|
||||||
|
const QString prefixPath = info.value(QStringLiteral("prefixPath")).toString();
|
||||||
|
const QVariantMap envOverrides = info.value(QStringLiteral("envOverrides")).toMap();
|
||||||
|
if (runner == QLatin1String("proton") || runner == QLatin1String("wine")) {
|
||||||
const QString resolvedPrefixPath = info.value(QStringLiteral("resolvedPrefixPath")).toString();
|
const QString resolvedPrefixPath = info.value(QStringLiteral("resolvedPrefixPath")).toString();
|
||||||
if (!resolvedPrefixPath.isEmpty()) {
|
if (!resolvedPrefixPath.isEmpty()) {
|
||||||
QDir().mkpath(resolvedPrefixPath);
|
QDir().mkpath(resolvedPrefixPath);
|
||||||
|
|
@ -390,10 +547,10 @@ void GameLauncher::launchGame(Game *game)
|
||||||
|
|
||||||
// Always try daemon first — for all launch types
|
// Always try daemon first — for all launch types
|
||||||
{
|
{
|
||||||
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"),
|
QDBusInterface iface(kGameCenterService,
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
kGameCenterPath,
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
kGameCenterInterface,
|
||||||
QDBusConnection::sessionBus());
|
m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus());
|
||||||
QVariantMap launchSpec = {
|
QVariantMap launchSpec = {
|
||||||
{QStringLiteral("command"), launchCommand},
|
{QStringLiteral("command"), launchCommand},
|
||||||
{QStringLiteral("gameId"), game->id()},
|
{QStringLiteral("gameId"), game->id()},
|
||||||
|
|
@ -401,6 +558,26 @@ void GameLauncher::launchGame(Game *game)
|
||||||
{QStringLiteral("provider"), provider},
|
{QStringLiteral("provider"), provider},
|
||||||
{QStringLiteral("origin"), QStringLiteral("ui")},
|
{QStringLiteral("origin"), QStringLiteral("ui")},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!runnerId.isEmpty()) {
|
||||||
|
launchSpec.insert(QStringLiteral("runnerId"), runnerId);
|
||||||
|
}
|
||||||
|
if (!runner.isEmpty()) {
|
||||||
|
launchSpec.insert(QStringLiteral("runner"), runner);
|
||||||
|
}
|
||||||
|
if (!runnerPath.isEmpty()) {
|
||||||
|
launchSpec.insert(QStringLiteral("runnerPath"), runnerPath);
|
||||||
|
}
|
||||||
|
if (!prefixPath.isEmpty()) {
|
||||||
|
launchSpec.insert(QStringLiteral("prefixPath"), prefixPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
launchSpec.insert(QStringLiteral("requestedProgram"), info.value(QStringLiteral("program")).toString());
|
||||||
|
launchSpec.insert(QStringLiteral("requestedArgs"), info.value(QStringLiteral("args")).toStringList());
|
||||||
|
if (!envOverrides.isEmpty()) {
|
||||||
|
launchSpec.insert(QStringLiteral("requestedEnvOverrides"), envOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
if (!finalProgram.isEmpty()) {
|
if (!finalProgram.isEmpty()) {
|
||||||
launchSpec.insert(QStringLiteral("program"), finalProgram);
|
launchSpec.insert(QStringLiteral("program"), finalProgram);
|
||||||
launchSpec.insert(QStringLiteral("args"), finalArgs);
|
launchSpec.insert(QStringLiteral("args"), finalArgs);
|
||||||
|
|
@ -427,6 +604,8 @@ void GameLauncher::launchGame(Game *game)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString launchError = reply.isValid() ? QString() : reply.error().message();
|
||||||
|
|
||||||
// Daemon call failed — fallback for URL-type commands only
|
// Daemon call failed — fallback for URL-type commands only
|
||||||
if (launchType == QLatin1String("url")) {
|
if (launchType == QLatin1String("url")) {
|
||||||
const QString url = info.value(QStringLiteral("url")).toString();
|
const QString url = info.value(QStringLiteral("url")).toString();
|
||||||
|
|
@ -439,7 +618,11 @@ void GameLauncher::launchGame(Game *game)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No fallback for non-URL commands — emit error
|
// No fallback for non-URL commands — emit error
|
||||||
Q_EMIT gameError(game, tr("Game Center daemon is not available"));
|
if (!launchError.isEmpty()) {
|
||||||
|
Q_EMIT gameError(game, tr("Game Center launch failed: %1").arg(launchError));
|
||||||
|
} else {
|
||||||
|
Q_EMIT gameError(game, tr("Game Center daemon is not available"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,10 +632,10 @@ void GameLauncher::stopGame(Game *game)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"),
|
QDBusInterface iface(kGameCenterService,
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
kGameCenterPath,
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
kGameCenterInterface,
|
||||||
QDBusConnection::sessionBus());
|
m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus());
|
||||||
iface.call(QStringLiteral("StopByGameId"), game->id());
|
iface.call(QStringLiteral("StopByGameId"), game->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -583,10 +766,10 @@ void GameLauncher::onDaemonLaunchFailed(const QVariantMap &error)
|
||||||
|
|
||||||
void GameLauncher::syncDaemonSessions()
|
void GameLauncher::syncDaemonSessions()
|
||||||
{
|
{
|
||||||
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"),
|
QDBusInterface iface(kGameCenterService,
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
kGameCenterPath,
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
kGameCenterInterface,
|
||||||
QDBusConnection::sessionBus());
|
m_usingSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus());
|
||||||
|
|
||||||
const QDBusReply<QVariantList> reply = iface.call(QStringLiteral("ListSessions"));
|
const QDBusReply<QVariantList> reply = iface.call(QStringLiteral("ListSessions"));
|
||||||
if (!reply.isValid()) {
|
if (!reply.isValid()) {
|
||||||
|
|
@ -623,13 +806,28 @@ void GameLauncher::applyRunningStateToLibrary()
|
||||||
|
|
||||||
void GameLauncher::checkDaemonAvailability()
|
void GameLauncher::checkDaemonAvailability()
|
||||||
{
|
{
|
||||||
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"),
|
const bool systemAvailable = pingDaemon(QDBusConnection::systemBus());
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
const bool sessionAvailable = systemAvailable ? false : pingDaemon(QDBusConnection::sessionBus());
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
const bool available = systemAvailable || sessionAvailable;
|
||||||
QDBusConnection::sessionBus());
|
const bool useSystemBus = systemAvailable;
|
||||||
|
|
||||||
|
disconnectDaemonSignals(QDBusConnection::systemBus(), this);
|
||||||
|
disconnectDaemonSignals(QDBusConnection::sessionBus(), this);
|
||||||
|
|
||||||
|
if (available) {
|
||||||
|
connectDaemonSignals(useSystemBus ? QDBusConnection::systemBus() : QDBusConnection::sessionBus(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool busChanged = m_usingSystemBus != useSystemBus;
|
||||||
|
if (busChanged) {
|
||||||
|
m_daemonGameToSession.clear();
|
||||||
|
m_daemonSessionToGame.clear();
|
||||||
|
Q_EMIT runningGamesChanged();
|
||||||
|
applyRunningStateToLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_usingSystemBus = useSystemBus;
|
||||||
|
|
||||||
const QDBusReply<QString> reply = iface.call(QStringLiteral("Ping"));
|
|
||||||
const bool available = reply.isValid() && reply.value() == QLatin1String("ok");
|
|
||||||
if (available != m_daemonAvailable) {
|
if (available != m_daemonAvailable) {
|
||||||
m_daemonAvailable = available;
|
m_daemonAvailable = available;
|
||||||
Q_EMIT daemonAvailableChanged();
|
Q_EMIT daemonAvailableChanged();
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ private:
|
||||||
QHash<QString, QString> m_daemonGameToSession;
|
QHash<QString, QString> m_daemonGameToSession;
|
||||||
QHash<QString, QString> m_daemonSessionToGame;
|
QHash<QString, QString> m_daemonSessionToGame;
|
||||||
bool m_daemonAvailable = false;
|
bool m_daemonAvailable = false;
|
||||||
|
bool m_usingSystemBus = false;
|
||||||
|
|
||||||
void checkDaemonAvailability();
|
void checkDaemonAvailability();
|
||||||
void syncDaemonSessions();
|
void syncDaemonSessions();
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,26 @@
|
||||||
|
|
||||||
K_PLUGIN_CLASS_WITH_JSON(AlakarteRunner, "plasma-runner-alakarte.json")
|
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)
|
AlakarteRunner::AlakarteRunner(QObject *parent, const KPluginMetaData &metaData)
|
||||||
: KRunner::AbstractRunner(parent, metaData)
|
: KRunner::AbstractRunner(parent, metaData)
|
||||||
{
|
{
|
||||||
|
|
@ -154,10 +174,6 @@ void AlakarteRunner::run(const KRunner::RunnerContext &context, const KRunner::Q
|
||||||
|
|
||||||
// Always try daemon first for all commands (including Steam/Lutris)
|
// Always try daemon first for all commands (including Steam/Lutris)
|
||||||
if (!match.id().isEmpty()) {
|
if (!match.id().isEmpty()) {
|
||||||
QDBusInterface iface(QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QStringLiteral("/org/kde/GameCenter1"),
|
|
||||||
QStringLiteral("org.kde.GameCenter1"),
|
|
||||||
QDBusConnection::sessionBus());
|
|
||||||
QVariantMap launchSpec = {
|
QVariantMap launchSpec = {
|
||||||
{QStringLiteral("command"), command},
|
{QStringLiteral("command"), command},
|
||||||
{QStringLiteral("gameId"), match.id()},
|
{QStringLiteral("gameId"), match.id()},
|
||||||
|
|
@ -165,8 +181,7 @@ void AlakarteRunner::run(const KRunner::RunnerContext &context, const KRunner::Q
|
||||||
{QStringLiteral("origin"), QStringLiteral("krunner")},
|
{QStringLiteral("origin"), QStringLiteral("krunner")},
|
||||||
};
|
};
|
||||||
|
|
||||||
const QDBusReply<QString> reply = iface.call(QStringLiteral("Launch"), launchSpec);
|
if (launchViaDaemon(QDBusConnection::systemBus(), launchSpec) || launchViaDaemon(QDBusConnection::sessionBus(), launchSpec)) {
|
||||||
if (reply.isValid() && !reply.value().isEmpty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue