a-la-karte/src/inputserviceclient.cpp

368 lines
9.4 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "inputserviceclient.h"
#include "input1interface.h"
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusError>
#include <QDBusPendingReply>
#include <QDBusReply>
#include <QDBusServiceWatcher>
#include <QDBusVariant>
static const QString kInputService = QStringLiteral("org.kde.ALaKarte.Input1");
static const QString kInputPath = QStringLiteral("/org/kde/ALaKarte/Input1");
static const QString kInputInterface = QStringLiteral("org.kde.ALaKarte.Input1");
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.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());
}
return m;
}
}
InputServiceClient::InputServiceClient(QObject *parent)
: QObject(parent)
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (bus.isConnected()) {
m_watcher = new QDBusServiceWatcher(kInputService, bus, QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, this);
connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() {
setAvailable(true);
connectSignals();
refresh();
});
connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
setAvailable(false);
m_controllers.clear();
m_profiles.clear();
Q_EMIT controllersChanged();
Q_EMIT profilesChanged();
});
}
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
m_connectedSignals = false;
});
}
ensureService();
refresh();
}
InputServiceClient::~InputServiceClient() = default;
bool InputServiceClient::available() const
{
return m_available;
}
QString InputServiceClient::lastError() const
{
return m_lastError;
}
QVariantList InputServiceClient::controllers() const
{
return m_controllers;
}
QVariantList InputServiceClient::profiles() const
{
return m_profiles;
}
void InputServiceClient::setAvailable(bool available)
{
if (m_available == available) {
return;
}
m_available = available;
Q_EMIT availableChanged();
}
void InputServiceClient::setLastError(const QString &error)
{
if (m_lastError == error) {
return;
}
m_lastError = error;
Q_EMIT lastErrorChanged();
}
void InputServiceClient::ensureService()
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected() || !bus.interface()) {
setAvailable(false);
setLastError(QStringLiteral("session bus not available"));
return;
}
if (bus.interface()->isServiceRegistered(kInputService)) {
setAvailable(true);
connectSignals();
return;
}
bool activatable = false;
{
const QDBusReply<QStringList> names = bus.interface()->activatableServiceNames();
if (names.isValid()) {
activatable = names.value().contains(kInputService);
}
}
const QDBusReply<void> reply = bus.interface()->startService(kInputService);
if (reply.isValid() || bus.interface()->isServiceRegistered(kInputService)) {
setAvailable(true);
connectSignals();
return;
}
setAvailable(false);
if (activatable) {
setLastError(reply.error().message());
} else {
setLastError(QStringLiteral("Input service is not available (missing DBus activation for org.kde.ALaKarte.Input1)"));
}
}
void InputServiceClient::connectSignals()
{
if (m_connectedSignals) {
return;
}
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected()) {
return;
}
const bool ok1 = bus.connect(kInputService, kInputPath, kInputInterface, QStringLiteral("ControllerAdded"), this, SLOT(onControllerAdded(QVariantMap)));
const bool ok2 = bus.connect(kInputService, kInputPath, kInputInterface, QStringLiteral("ControllerChanged"), this, SLOT(onControllerChanged(QVariantMap)));
const bool ok3 = bus.connect(kInputService, kInputPath, kInputInterface, QStringLiteral("ControllerRemoved"), this, SLOT(onControllerRemoved(QString)));
const bool ok4 = bus.connect(kInputService, kInputPath, kInputInterface, QStringLiteral("ProfilesChanged"), this, SLOT(onProfilesChanged()));
m_connectedSignals = ok1 && ok2 && ok3 && ok4;
}
void InputServiceClient::onControllerAdded(QVariantMap controller)
{
upsertController(normalizeMap(controller));
}
void InputServiceClient::onControllerChanged(QVariantMap controller)
{
upsertController(normalizeMap(controller));
}
void InputServiceClient::onControllerRemoved(const QString &controllerId)
{
removeControllerById(controllerId);
}
void InputServiceClient::onProfilesChanged()
{
refreshProfiles();
}
void InputServiceClient::upsertController(const QVariantMap &controller)
{
const QString id = controller.value(QStringLiteral("controllerId")).toString();
if (id.isEmpty()) {
return;
}
for (int i = 0; i < m_controllers.size(); ++i) {
const QVariantMap existing = m_controllers.at(i).toMap();
if (existing.value(QStringLiteral("controllerId")).toString() == id) {
m_controllers[i] = controller;
Q_EMIT controllersChanged();
return;
}
}
m_controllers.push_back(controller);
Q_EMIT controllersChanged();
}
void InputServiceClient::removeControllerById(const QString &controllerId)
{
if (controllerId.isEmpty()) {
return;
}
for (int i = 0; i < m_controllers.size(); ++i) {
const QVariantMap existing = m_controllers.at(i).toMap();
if (existing.value(QStringLiteral("controllerId")).toString() == controllerId) {
m_controllers.removeAt(i);
Q_EMIT controllersChanged();
return;
}
}
}
void InputServiceClient::refresh()
{
refreshControllers();
refreshProfiles();
}
void InputServiceClient::refreshControllers()
{
ensureService();
if (!m_available) {
return;
}
org::kde::ALaKarte::Input1 iface(kInputService, kInputPath, QDBusConnection::sessionBus());
iface.setTimeout(2000);
{
QDBusPendingReply<bool> rescanReply = iface.Rescan();
rescanReply.waitForFinished();
if (rescanReply.isError()) {
setLastError(rescanReply.error().message());
}
}
QDBusPendingReply<QVariantList> reply = iface.ListControllers();
reply.waitForFinished();
if (reply.isError()) {
setLastError(reply.error().message());
return;
}
m_controllers.clear();
const QVariantList list = reply.value();
m_controllers.reserve(list.size());
for (const QVariant &v : list) {
m_controllers.push_back(unwrapVariantMap(v));
}
Q_EMIT controllersChanged();
}
void InputServiceClient::refreshProfiles()
{
ensureService();
if (!m_available) {
return;
}
org::kde::ALaKarte::Input1 iface(kInputService, kInputPath, QDBusConnection::sessionBus());
iface.setTimeout(2000);
QDBusPendingReply<QVariantList> reply = iface.ListProfiles();
reply.waitForFinished();
if (reply.isError()) {
setLastError(reply.error().message());
return;
}
m_profiles.clear();
const QVariantList list = reply.value();
m_profiles.reserve(list.size());
for (const QVariant &v : list) {
m_profiles.push_back(unwrapVariantMap(v));
}
Q_EMIT profilesChanged();
}
bool InputServiceClient::setActiveProfile(const QString &controllerId, const QString &profileId)
{
ensureService();
if (!m_available) {
return false;
}
org::kde::ALaKarte::Input1 iface(kInputService, kInputPath, QDBusConnection::sessionBus());
iface.setTimeout(2000);
QDBusPendingReply<bool> reply = iface.SetActiveProfile(controllerId, profileId);
reply.waitForFinished();
if (reply.isError()) {
setLastError(reply.error().message());
return false;
}
return reply.value();
}
QString InputServiceClient::createProfile(const QString &name)
{
ensureService();
if (!m_available) {
return {};
}
org::kde::ALaKarte::Input1 iface(kInputService, kInputPath, QDBusConnection::sessionBus());
iface.setTimeout(2000);
QDBusPendingReply<QString> reply = iface.CreateProfile(name);
reply.waitForFinished();
if (reply.isError()) {
setLastError(reply.error().message());
return {};
}
refreshProfiles();
return reply.value();
}
bool InputServiceClient::deleteProfile(const QString &profileId)
{
ensureService();
if (!m_available) {
return false;
}
org::kde::ALaKarte::Input1 iface(kInputService, kInputPath, QDBusConnection::sessionBus());
iface.setTimeout(2000);
QDBusPendingReply<bool> reply = iface.DeleteProfile(profileId);
reply.waitForFinished();
if (reply.isError()) {
setLastError(reply.error().message());
return false;
}
refreshProfiles();
refreshControllers();
return reply.value();
}