Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdevicediscovery_udev.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QStringList>
7#include <QCoreApplication>
8#include <QObject>
9#include <QHash>
10#include <QSocketNotifier>
11#include <QLoggingCategory>
12
13#ifdef Q_OS_FREEBSD
14#include <dev/evdev/input.h>
15#else
16#include <linux/input.h>
17#endif
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23Q_STATIC_LOGGING_CATEGORY(lcDD, "qt.qpa.input")
24
25QDeviceDiscovery *QDeviceDiscovery::create(QDeviceTypes types, QObject *parent)
26{
27 qCDebug(lcDD) << "udev device discovery for type" << types;
28
29 QDeviceDiscovery *helper = nullptr;
30 struct udev *udev;
31
32 udev = udev_new();
33 if (udev) {
34 helper = new QDeviceDiscoveryUDev(types, udev, parent);
35 } else {
36 qWarning("Failed to get udev library context");
37 }
38
39 return helper;
40}
41
42QDeviceDiscoveryUDev::QDeviceDiscoveryUDev(QDeviceTypes types, struct udev *udev, QObject *parent) :
43 QDeviceDiscovery(types, parent),
44 m_udev(udev)
45{
46 if (!m_udev)
47 return;
48
49 m_udevMonitor = udev_monitor_new_from_netlink(m_udev, "udev");
50 if (!m_udevMonitor) {
51 qWarning("Unable to create an udev monitor. No devices can be detected.");
52 return;
53 }
54
55 udev_monitor_filter_add_match_subsystem_devtype(m_udevMonitor, "input", 0);
56 udev_monitor_filter_add_match_subsystem_devtype(m_udevMonitor, "drm", 0);
57 udev_monitor_enable_receiving(m_udevMonitor);
58 m_udevMonitorFileDescriptor = udev_monitor_get_fd(m_udevMonitor);
59
60 m_udevSocketNotifier = new QSocketNotifier(m_udevMonitorFileDescriptor, QSocketNotifier::Read, this);
61 connect(m_udevSocketNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleUDevNotification()));
62}
63
64QDeviceDiscoveryUDev::~QDeviceDiscoveryUDev()
65{
66 if (m_udevMonitor)
67 udev_monitor_unref(m_udevMonitor);
68
69 if (m_udev)
70 udev_unref(m_udev);
71}
72
73QStringList QDeviceDiscoveryUDev::scanConnectedDevices()
74{
75 QStringList devices;
76
77 if (!m_udev)
78 return devices;
79
80 udev_enumerate *ue = udev_enumerate_new(m_udev);
81 udev_enumerate_add_match_subsystem(ue, "input");
82 udev_enumerate_add_match_subsystem(ue, "drm");
83
84 if (m_types & Device_Mouse)
85 udev_enumerate_add_match_property(ue, "ID_INPUT_MOUSE", "1");
86 if (m_types & Device_Touchpad)
87 udev_enumerate_add_match_property(ue, "ID_INPUT_TOUCHPAD", "1");
88 if (m_types & Device_Touchscreen)
89 udev_enumerate_add_match_property(ue, "ID_INPUT_TOUCHSCREEN", "1");
90 if (m_types & Device_Keyboard) {
91 udev_enumerate_add_match_property(ue, "ID_INPUT_KEYBOARD", "1");
92 udev_enumerate_add_match_property(ue, "ID_INPUT_KEY", "1");
93 }
94 if (m_types & Device_Tablet)
95 udev_enumerate_add_match_property(ue, "ID_INPUT_TABLET", "1");
96 if (m_types & Device_Joystick)
97 udev_enumerate_add_match_property(ue, "ID_INPUT_JOYSTICK", "1");
98
99 if (udev_enumerate_scan_devices(ue) != 0) {
100 qWarning("Failed to scan devices");
101 return devices;
102 }
103
104 udev_list_entry *entry;
105 udev_list_entry_foreach (entry, udev_enumerate_get_list_entry(ue)) {
106 const char *syspath = udev_list_entry_get_name(entry);
107 udev_device *udevice = udev_device_new_from_syspath(m_udev, syspath);
108 if (!udevice)
109 continue;
110 QString candidate = QString::fromUtf8(udev_device_get_devnode(udevice));
111 if ((m_types & Device_InputMask) && candidate.startsWith(QT_EVDEV_DEVICE ""_L1))
112 devices << candidate;
113 if ((m_types & Device_VideoMask) && candidate.startsWith(QT_DRM_DEVICE ""_L1)) {
114 if (m_types & Device_DRM_PrimaryGPU) {
115 udev_device *pci = udev_device_get_parent_with_subsystem_devtype(udevice, "pci", 0);
116 if (pci) {
117 if (qstrcmp(udev_device_get_sysattr_value(pci, "boot_vga"), "1") == 0)
118 devices << candidate;
119 }
120 } else
121 devices << candidate;
122 }
123
124 udev_device_unref(udevice);
125 }
126 udev_enumerate_unref(ue);
127
128 qCDebug(lcDD) << "Found matching devices" << devices;
129
130 return devices;
131}
132
133void QDeviceDiscoveryUDev::handleUDevNotification()
134{
135 if (!m_udevMonitor)
136 return;
137
138 struct udev_device *dev;
139 QString devNode;
140
141 dev = udev_monitor_receive_device(m_udevMonitor);
142 if (!dev)
143 goto cleanup;
144
145 const char *action;
146 action = udev_device_get_action(dev);
147 if (!action)
148 goto cleanup;
149
150 const char *str;
151 str = udev_device_get_devnode(dev);
152 if (!str)
153 goto cleanup;
154
155 const char *subsystem;
156 devNode = QString::fromUtf8(str);
157 if (devNode.startsWith(QT_EVDEV_DEVICE ""_L1))
158 subsystem = "input";
159 else if (devNode.startsWith(QT_DRM_DEVICE ""_L1))
160 subsystem = "drm";
161 else goto cleanup;
162
163 // if we cannot determine a type, walk up the device tree
164 if (!checkDeviceType(dev)) {
165 // does not increase the refcount
166 struct udev_device *parent_dev = udev_device_get_parent_with_subsystem_devtype(dev, subsystem, 0);
167 if (!parent_dev)
168 goto cleanup;
169
170 if (!checkDeviceType(parent_dev))
171 goto cleanup;
172 }
173
174 if (qstrcmp(action, "add") == 0)
175 emit deviceDetected(devNode);
176
177 if (qstrcmp(action, "remove") == 0)
178 emit deviceRemoved(devNode);
179
180 if (qstrcmp(action, "change") == 0)
181 emit deviceChanged(devNode);
182
183cleanup:
184 udev_device_unref(dev);
185}
186
187bool QDeviceDiscoveryUDev::checkDeviceType(udev_device *dev)
188{
189 if (!dev)
190 return false;
191
192 if ((m_types & Device_Keyboard) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"), "1") == 0 )) {
193 const QString capabilities_key = QString::fromUtf8(udev_device_get_sysattr_value(dev, "capabilities/key"));
194 const auto val = QStringView{capabilities_key}.split(u' ', Qt::SkipEmptyParts);
195 if (!val.isEmpty()) {
196 bool ok;
197 unsigned long long keys = val.last().toULongLong(&ok, 16);
198 if (ok) {
199 // Tests if the letter Q is valid for the device. We may want to alter this test, but it seems mostly reliable.
200 bool test = (keys >> KEY_Q) & 1;
201 if (test)
202 return true;
203 }
204 }
205 }
206
207 if ((m_types & Device_Keyboard) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_KEY"), "1") == 0 ))
208 return true;
209
210 if ((m_types & Device_Mouse) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_MOUSE"), "1") == 0))
211 return true;
212
213 if ((m_types & Device_Touchpad) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD"), "1") == 0))
214 return true;
215
216 if ((m_types & Device_Touchscreen) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"), "1") == 0))
217 return true;
218
219 if ((m_types & Device_Tablet) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TABLET"), "1") == 0))
220 return true;
221
222 if ((m_types & Device_Joystick) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"), "1") == 0))
223 return true;
224
225 if ((m_types & Device_DRM) && (qstrcmp(udev_device_get_subsystem(dev), "drm") == 0))
226 return true;
227
228 return false;
229}
230
231QT_END_NAMESPACE
232
233#include "moc_qdevicediscovery_udev_p.cpp"
#define QT_EVDEV_DEVICE
#define QT_DRM_DEVICE
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)