mirror of
https://invent.kde.org/marcoa/a-la-karte.git
synced 2026-03-27 01:03:09 +00:00
368 lines
9.4 KiB
C++
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();
|
|
}
|