summaryrefslogtreecommitdiffstats
path: root/tests/manual/inputdevices/inputdevicemodel.cpp
blob: c0b594a7da1c282a1411a4aed11989f73a4fce8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#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"