summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/manual/inputdevices/CMakeLists.txt17
-rw-r--r--tests/manual/inputdevices/inputdevicemodel.cpp264
-rw-r--r--tests/manual/inputdevices/inputdevicemodel.h55
-rw-r--r--tests/manual/inputdevices/main.cpp24
4 files changed, 360 insertions, 0 deletions
diff --git a/tests/manual/inputdevices/CMakeLists.txt b/tests/manual/inputdevices/CMakeLists.txt
new file mode 100644
index 0000000000..9440705a9b
--- /dev/null
+++ b/tests/manual/inputdevices/CMakeLists.txt
@@ -0,0 +1,17 @@
+project(inputdevices)
+cmake_minimum_required(VERSION 3.19)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
+
+qt_add_executable(inputdevices
+ main.cpp
+ inputdevicemodel.h inputdevicemodel.cpp
+)
+
+set_target_properties(inputdevices PROPERTIES
+ AUTOMOC TRUE
+)
+
+target_link_libraries(inputdevices PUBLIC
+ Qt::Widgets
+)
diff --git a/tests/manual/inputdevices/inputdevicemodel.cpp b/tests/manual/inputdevices/inputdevicemodel.cpp
new file mode 100644
index 0000000000..8168ae64bf
--- /dev/null
+++ b/tests/manual/inputdevices/inputdevicemodel.cpp
@@ -0,0 +1,264 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "inputdevicemodel.h"
+
+#include <QEvent>
+#include <QInputDevice>
+#include <QLoggingCategory>
+#include <QMetaEnum>
+#include <QPointingDevice>
+
+Q_LOGGING_CATEGORY(lcIPDM, "qt.inputdevicemodel")
+
+static QString enumToString(const QObject *obj, const char* enumName, int enumValue)
+{
+ const auto *metaobj = obj->metaObject();
+ Q_ASSERT(metaobj);
+ const int enumIdx = metaobj->indexOfEnumerator(enumName);
+ if (enumIdx < 0)
+ return {};
+ Q_ASSERT(metaobj->enumerator(enumIdx).isValid());
+ const char *ret = metaobj->enumerator(enumIdx).valueToKey(enumValue);
+ if (!ret)
+ return {};
+ return QString::fromUtf8(ret);
+}
+
+static QString capabilitiesString(const QInputDevice *dev)
+{
+ QStringList ret;
+ const auto caps = dev->capabilities();
+ if (caps.testFlag(QInputDevice::Capability::Position))
+ ret << InputDeviceModel::tr("pos");
+ if (caps.testFlag(QInputDevice::Capability::Area))
+ ret << InputDeviceModel::tr("area");
+ if (caps.testFlag(QInputDevice::Capability::Pressure))
+ ret << InputDeviceModel::tr("press");
+ if (caps.testFlag(QInputDevice::Capability::Velocity))
+ ret << InputDeviceModel::tr("vel");
+ if (caps.testFlag(QInputDevice::Capability::NormalizedPosition))
+ ret << InputDeviceModel::tr("norm");
+ if (caps.testFlag(QInputDevice::Capability::MouseEmulation))
+ ret << InputDeviceModel::tr("m-emu");
+ if (caps.testFlag(QInputDevice::Capability::Scroll))
+ ret << InputDeviceModel::tr("scroll");
+ if (caps.testFlag(QInputDevice::Capability::PixelScroll))
+ ret << InputDeviceModel::tr("pxscroll");
+ if (caps.testFlag(QInputDevice::Capability::Hover))
+ ret << InputDeviceModel::tr("hover");
+ if (caps.testFlag(QInputDevice::Capability::Rotation))
+ ret << InputDeviceModel::tr("rot");
+ if (caps.testFlag(QInputDevice::Capability::XTilt))
+ ret << InputDeviceModel::tr("xtilt");
+ if (caps.testFlag(QInputDevice::Capability::YTilt))
+ ret << InputDeviceModel::tr("ytilt");
+ if (caps.testFlag(QInputDevice::Capability::TangentialPressure))
+ ret << InputDeviceModel::tr("tan");
+ if (caps.testFlag(QInputDevice::Capability::ZPosition))
+ ret << InputDeviceModel::tr("z");
+ return ret.join(u'|');
+}
+
+/*!
+ Returns true only if the given \a device is a master:
+ that is, if its parent is \e not another QInputDevice.
+*/
+static const auto masterDevicePred = [](const QInputDevice *device)
+{
+ return !qobject_cast<QInputDevice*>(device->parent());
+};
+
+/*!
+ Returns the master device at index \a i:
+ that is, the i'th of the devices that satisfy masterDevicePred().
+*/
+static const QInputDevice *masterDevice(int i)
+{
+ const auto devices = QInputDevice::devices();
+ auto it = std::find_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
+ it += i;
+ return (it == devices.constEnd() ? nullptr : *it);
+}
+
+/*!
+ Returns the index of the master \a device: that is, the index within the
+ subset of QInputDevice::devices() that satisfy masterDevicePred().
+*/
+static const int masterDeviceIndex(const QInputDevice *device)
+{
+ Q_ASSERT(masterDevicePred(device)); // assume dev is not a slave
+ const auto devices = QInputDevice::devices();
+ auto it = std::find_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
+ for (int i = 0; it != devices.constEnd(); ++i, ++it)
+ if (*it == device)
+ return i;
+ return -1;
+}
+
+InputDeviceModel::InputDeviceModel(QObject *parent)
+ : QAbstractItemModel{parent}
+{
+ connect(this, &InputDeviceModel::deviceAdded, this, &InputDeviceModel::onDeviceAdded, Qt::QueuedConnection);
+}
+
+// invariant: always call createIndex(row, column, QInputDevice*) or else nullptr for the last argument
+
+QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const
+{
+ const QInputDevice *par = static_cast<QInputDevice *>(parent.internalPointer());
+ const QInputDevice *ret = par ? qobject_cast<const QInputDevice *>(par->children().at(row)) : masterDevice(row);
+ qCDebug(lcIPDM) << row << column << "under parent" << par << ":" << ret;
+ return createIndex(row, column, ret);
+}
+
+QModelIndex InputDeviceModel::parent(const QModelIndex &index) const
+{
+ if (!index.internalPointer())
+ return {};
+ const QInputDevice *par = qobject_cast<const QInputDevice *>(
+ static_cast<QInputDevice *>(index.internalPointer())->parent());
+ if (par)
+ return createIndex(masterDeviceIndex(par), index.column(), par);
+ return {};
+}
+
+int InputDeviceModel::rowCount(const QModelIndex &parent) const
+{
+ int ret = 0;
+ const QInputDevice *par = qobject_cast<const QInputDevice *>(static_cast<QObject *>(parent.internalPointer()));
+ if (par) {
+ ret = par->children().count();
+ } else {
+ const auto devices = QInputDevice::devices();
+ ret = std::count_if(devices.constBegin(), devices.constEnd(), masterDevicePred);
+ }
+ qCDebug(lcIPDM) << ret << "under parent" << parent << par;
+ return ret;
+}
+
+int InputDeviceModel::columnCount(const QModelIndex &) const
+{
+ return NRoles;
+}
+
+QVariant InputDeviceModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch (section + Name) {
+ case Name:
+ return tr("Device Name");
+ case DeviceType:
+ return tr("Device Type");
+ case PointerType:
+ return tr("Pointer Type");
+ case Capabilities:
+ return tr("Capabilities");
+ case SystemId:
+ return tr("System ID");
+ case SeatName:
+ return tr("Seat Name");
+ case AvailableVirtualGeometry:
+ return tr("Available Virtual Geometry");
+ case MaximumPoints:
+ return tr("Maximum Points");
+ case ButtonCount:
+ return tr("Button Count");
+ case UniqueId:
+ return tr("Unique ID");
+ case NRoles:
+ break;
+ }
+ }
+ return {};
+}
+
+bool InputDeviceModel::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::ChildAdded)
+ // At this time, the child is not fully-constructed.
+ // Emit a signal which is connected to onDeviceAdded via queued connection, to delay row insertion.
+ emit deviceAdded(static_cast<QChildEvent *>(event)->child());
+ return false;
+}
+
+void InputDeviceModel::onDeviceAdded(const QObject *o)
+{
+ const QInputDevice *child = qobject_cast<const QInputDevice *>(o);
+ const QInputDevice *parent = qobject_cast<const QInputDevice *>(child->parent());
+ const int idx = parent->children().indexOf(child);
+ qCDebug(lcIPDM) << parent << "has a baby!" << child << "@" << idx;
+ beginInsertRows(createIndex(masterDeviceIndex(parent), 0, parent), idx, idx);
+ endInsertRows();
+}
+
+void InputDeviceModel::watchDevice(const QInputDevice *dev) const
+{
+ if (!m_known.contains(dev)) {
+ m_known << dev;
+ connect(dev, &QObject::destroyed, this, &InputDeviceModel::deviceDestroyed);
+ if (masterDevicePred(dev))
+ const_cast<QInputDevice *>(dev)->installEventFilter(const_cast<InputDeviceModel *>(this));
+ }
+}
+
+void InputDeviceModel::deviceDestroyed(QObject *o)
+{
+ beginResetModel();
+ const QInputDevice *dev = static_cast<QInputDevice *>(o);
+ bool needsReset = true;
+ if (!masterDevicePred(dev)) {
+ const QInputDevice *parent = static_cast<const QInputDevice *>(dev->parent());
+ const int idx = parent->children().indexOf(dev);
+ Q_ASSERT(idx >= 0);
+ beginRemoveRows(createIndex(masterDeviceIndex(parent), 0, parent), idx, idx);
+ endRemoveRows();
+ needsReset = false;
+ }
+ m_known.removeOne(dev);
+ if (needsReset)
+ endResetModel();
+}
+
+QVariant InputDeviceModel::data(const QModelIndex &index, int role) const
+{
+ if (role == Qt::DisplayRole)
+ role = index.column() + Role::Name;
+ if (role >= NRoles)
+ return {};
+ const QInputDevice *dev = static_cast<QInputDevice *>(index.internalPointer());
+ watchDevice(dev);
+ if (role < Name)
+ qCDebug(lcIPDM) << index << Qt::ItemDataRole(role) << dev;
+ else
+ qCDebug(lcIPDM) << index << Role(role) << dev;
+ const QPointingDevice *pdev = qobject_cast<const QPointingDevice *>(dev);
+ switch (role) {
+ case Name:
+ return dev->name();
+ case DeviceType:
+ return enumToString(dev, "DeviceType", int(dev->type()));
+ case PointerType:
+ return pdev ? enumToString(pdev, "PointerType", int(pdev->pointerType()))
+ : QString();
+ case Capabilities:
+ return capabilitiesString(dev);
+ case SystemId:
+ return dev->systemId();
+ case SeatName:
+ return dev->seatName();
+ case AvailableVirtualGeometry: {
+ const auto rect = dev->availableVirtualGeometry();
+ return tr("%1 x %2 %3 %4").arg(rect.width()).arg(rect.height()).arg(rect.x()).arg(rect.y());
+ }
+ case MaximumPoints:
+ return pdev ? pdev->maximumPoints() : 0;
+ case ButtonCount:
+ return pdev ? pdev->buttonCount() : 0;
+ case UniqueId:
+ return pdev ? pdev->uniqueId().numericId() : 0;
+ }
+ return {};
+}
+
+#include "moc_inputdevicemodel.cpp"
diff --git a/tests/manual/inputdevices/inputdevicemodel.h b/tests/manual/inputdevices/inputdevicemodel.h
new file mode 100644
index 0000000000..c68e64e95d
--- /dev/null
+++ b/tests/manual/inputdevices/inputdevicemodel.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef INPUTDEVICEMODEL_H
+#define INPUTDEVICEMODEL_H
+
+#include <QAbstractItemModel>
+
+class QInputDevice;
+
+class InputDeviceModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ enum Role {
+ Name = Qt::UserRole + 1,
+ DeviceType,
+ PointerType,
+ Capabilities,
+ SystemId,
+ SeatName,
+ AvailableVirtualGeometry,
+ MaximumPoints,
+ ButtonCount,
+ UniqueId,
+ NRoles
+ };
+ Q_ENUM(Role);
+
+ explicit InputDeviceModel(QObject *parent = nullptr);
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+protected:
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+signals:
+ void deviceAdded(const QObject *o);
+
+private slots:
+ void onDeviceAdded(const QObject *o);
+
+private:
+ void watchDevice(const QInputDevice *dev) const;
+ void deviceDestroyed(QObject *o);
+
+ mutable QList<const QInputDevice *> m_known;
+};
+
+#endif // INPUTDEVICEMODEL_H
diff --git a/tests/manual/inputdevices/main.cpp b/tests/manual/inputdevices/main.cpp
new file mode 100644
index 0000000000..4b4386ae6f
--- /dev/null
+++ b/tests/manual/inputdevices/main.cpp
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QLoggingCategory>
+#include <QTreeView>
+
+#include "inputdevicemodel.h"
+
+int main(int argc, char **argv)
+{
+ QLoggingCategory::setFilterRules(QStringLiteral("qt.qpa.input.devices=true"));
+
+ QApplication app(argc, argv);
+
+ QTreeView view;
+ view.setModel(new InputDeviceModel(&view));
+ view.resize(1280, 600);
+ view.show();
+ view.resizeColumnToContents(0);
+
+ app.exec();
+}
+