SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-FileCopyrightText: 2026 Kai Uwe Broulik <kde@broulik.de>
Work sponsored by Technische UniversitΓ€t Dresden:
SPDX-FileCopyrightText: 2022 KlarΓ€lvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "devicesmodel.h"
#include <utility>
#include <QDBusMessage>
#include "InputDevice_interface.h"
#include "inputdevice.h"
#include "kwindevices-logging.h"
namespace KWinDevices
{
static constexpr QLatin1String g_kwinService{"org.kde.KWin"};
static constexpr QLatin1String g_kwinInputDevicePath{"/org/kde/KWin/InputDevice"};
static constexpr QLatin1String g_kwinInputDeviceManagerInterface{"org.kde.KWin.InputDeviceManager"};
using namespace Qt::StringLiterals;
DevicesModel::DevicesModel(DevicesModel::Kind kind, QObject *parent)
: DevicesModel(kind, {}, parent)
{
}
DevicesModel::DevicesModel(DevicesModel::Kind kind, const QVariantMap &filters, QObject *parent)
: QAbstractListModel(parent)
, m_kind(kind)
, m_filters(filters)
{
QDBusConnection::sessionBus()
.connect(g_kwinService, g_kwinInputDevicePath, g_kwinInputDeviceManagerInterface, u"deviceAdded"_s, this, SLOT(onDeviceAdded(QString)));
QDBusConnection::sessionBus()
.connect(g_kwinService, g_kwinInputDevicePath, g_kwinInputDeviceManagerInterface, u"deviceRemoved"_s, this, SLOT(onDeviceRemoved(QString)));
QString listMethod;
switch (kind) {
case Kind::Pointers:
listMethod = u"ListPointers"_s;
break;
case Kind::Keyboards:
listMethod = u"ListKeyboards"_s;
break;
case Kind::Touch:
listMethod = u"ListTouch"_s;
break;
}
auto msg = QDBusMessage::createMethodCall(g_kwinService, g_kwinInputDevicePath, g_kwinInputDeviceManagerInterface, listMethod);
auto reply = QDBusConnection::sessionBus().call(msg);
if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().isEmpty()) {
qCWarning(LIBKWINDEVICES) << "Error getting device list from KWin" << reply.errorMessage();
m_errorString = reply.errorMessage();
return;
}
const QStringList deviceSysNames = reply.arguments().at(0).toStringList();
for (const QString &sysName : deviceSysNames) {
bool relevantDevice = true;
if (!m_filters.isEmpty()) {
org::kde::KWin::InputDevice deviceIface{g_kwinService, g_kwinInputDevicePath + '/'_L1 + sysName, QDBusConnection::sessionBus()};
for (const auto &[key, value] : std::as_const(m_filters).asKeyValueRange()) {
if (deviceIface.property(qUtf8Printable(key)) != value) {
relevantDevice = false;
break;
}
}
}
if (relevantDevice) {
addDevice(sysName);
}
}
}
QVariant DevicesModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid) || index.column() != 0) {
return {};
}
switch (role) {
case Qt::DisplayRole:
return m_devices.at(index.row())->name();
case DeviceRole:
return QVariant::fromValue<QObject *>(m_devices.at(index.row()).get());
case SysNameRole:
return m_deviceSysNames.at(index.row());
}
return {};
}
int DevicesModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : m_devices.size();
}
void DevicesModel::onDeviceAdded(const QString &sysName)
{
const int index = m_deviceSysNames.indexOf(sysName);
if (index > -1) {
return;
}
org::kde::KWin::InputDevice deviceIface{g_kwinService, g_kwinInputDevicePath + '/'_L1 + sysName, QDBusConnection::sessionBus()};
switch (m_kind) {
case Kind::Keyboards:
if (!deviceIface.keyboard()) {
return;
}
break;
case Kind::Pointers:
if (!deviceIface.pointer()) {
return;
}
break;
case Kind::Touch:
if (!deviceIface.touch()) {
return;
}
break;
}
for (const auto &[key, value] : std::as_const(m_filters).asKeyValueRange()) {
if (deviceIface.property(qUtf8Printable(key)) != value) {
return;
}
}
beginInsertRows({}, m_devices.size(), m_devices.size());
addDevice(sysName);
endInsertRows();
}
void DevicesModel::addDevice(const QString &sysName)
{
auto dev = std::make_unique<InputDevice>(sysName, this);
connect(dev.get(), &InputDevice::needsSaveChanged, this, &DevicesModel::needsSaveChanged);
qCDebug(LIBKWINDEVICES).nospace() << "Device connected:" << dev->name() << " (" << dev->sysName() << ")";
m_devices.push_back(std::move(dev));
m_deviceSysNames.append(sysName);
}
void DevicesModel::onDeviceRemoved(const QString &sysName)
{
const int index = m_deviceSysNames.indexOf(sysName);
if (index == -1) {
return;
}
qCDebug(LIBKWINDEVICES) << "Device disconnected:" << sysName;
beginRemoveRows({}, index, index);
m_devices.erase(m_devices.begin() + index);
m_deviceSysNames.remove(index);
endRemoveRows();
Q_EMIT deviceRemoved(sysName);
}
InputDevice *DevicesModel::deviceAt(int row) const
{
if (row == -1) {
return nullptr;
}
return m_devices.at(row).get();
}
QHash<int, QByteArray> DevicesModel::roleNames() const
{
return {
{Qt::DisplayRole, QByteArrayLiteral("display")},
{DeviceRole, QByteArrayLiteral("device")},
{SysNameRole, QByteArrayLiteral("sysName")},
};
}
void DevicesModel::defaults()
{
for (auto &device : m_devices) {
device->defaults();
}
}
void DevicesModel::load()
{
for (auto &device : m_devices) {
device->load();
}
}
void DevicesModel::save()
{
for (auto &device : m_devices) {
device->save();
}
}
bool DevicesModel::isDefaults() const
{
return std::all_of(m_devices.cbegin(), m_devices.cend(), [](auto &dev) {
return dev->isDefaults();
});
}
bool DevicesModel::isSaveNeeded() const
{
return std::any_of(m_devices.cbegin(), m_devices.cend(), [](auto &dev) {
return dev->isSaveNeeded();
});
}
QString DevicesModel::errorString() const
{
return m_errorString;
}
}
#include "moc_devicesmodel.cpp"