/* SPDX-FileCopyrightText: 2018 Roman Gilg SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2026 Kai Uwe Broulik Work sponsored by Technische Universität Dresden: SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB a KDAB Group company SPDX-License-Identifier: GPL-2.0-or-later */ #include "devicesmodel.h" #include #include #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(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(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 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; } } // namespace KWinDevices #include "moc_devicesmodel.cpp"