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
4#include "qdevicediscovery_udev_p.h"
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_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(msg: "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(udev: m_udev, name: "udev");
50 if (!m_udevMonitor) {
51 qWarning(msg: "Unable to create an udev monitor. No devices can be detected.");
52 return;
53 }
54
55 udev_monitor_filter_add_match_subsystem_devtype(udev_monitor: m_udevMonitor, subsystem: "input", devtype: 0);
56 udev_monitor_filter_add_match_subsystem_devtype(udev_monitor: m_udevMonitor, subsystem: "drm", devtype: 0);
57 udev_monitor_enable_receiving(udev_monitor: m_udevMonitor);
58 m_udevMonitorFileDescriptor = udev_monitor_get_fd(udev_monitor: m_udevMonitor);
59
60 m_udevSocketNotifier = new QSocketNotifier(m_udevMonitorFileDescriptor, QSocketNotifier::Read, this);
61 connect(sender: m_udevSocketNotifier, SIGNAL(activated(QSocketDescriptor)), receiver: this, SLOT(handleUDevNotification()));
62}
63
64QDeviceDiscoveryUDev::~QDeviceDiscoveryUDev()
65{
66 if (m_udevMonitor)
67 udev_monitor_unref(udev_monitor: m_udevMonitor);
68
69 if (m_udev)
70 udev_unref(udev: 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(udev: m_udev);
81 udev_enumerate_add_match_subsystem(udev_enumerate: ue, subsystem: "input");
82 udev_enumerate_add_match_subsystem(udev_enumerate: ue, subsystem: "drm");
83
84 if (m_types & Device_Mouse)
85 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_MOUSE", value: "1");
86 if (m_types & Device_Touchpad)
87 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TOUCHPAD", value: "1");
88 if (m_types & Device_Touchscreen)
89 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TOUCHSCREEN", value: "1");
90 if (m_types & Device_Keyboard) {
91 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_KEYBOARD", value: "1");
92 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_KEY", value: "1");
93 }
94 if (m_types & Device_Tablet)
95 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TABLET", value: "1");
96 if (m_types & Device_Joystick)
97 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_JOYSTICK", value: "1");
98
99 if (udev_enumerate_scan_devices(udev_enumerate: ue) != 0) {
100 qWarning(msg: "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(list_entry: entry);
107 udev_device *udevice = udev_device_new_from_syspath(udev: m_udev, syspath);
108 QString candidate = QString::fromUtf8(utf8: udev_device_get_devnode(udev_device: udevice));
109 if ((m_types & Device_InputMask) && candidate.startsWith(QT_EVDEV_DEVICE ""_L1))
110 devices << candidate;
111 if ((m_types & Device_VideoMask) && candidate.startsWith(QT_DRM_DEVICE ""_L1)) {
112 if (m_types & Device_DRM_PrimaryGPU) {
113 udev_device *pci = udev_device_get_parent_with_subsystem_devtype(udev_device: udevice, subsystem: "pci", devtype: 0);
114 if (pci) {
115 if (qstrcmp(str1: udev_device_get_sysattr_value(udev_device: pci, sysattr: "boot_vga"), str2: "1") == 0)
116 devices << candidate;
117 }
118 } else
119 devices << candidate;
120 }
121
122 udev_device_unref(udev_device: udevice);
123 }
124 udev_enumerate_unref(udev_enumerate: ue);
125
126 qCDebug(lcDD) << "Found matching devices" << devices;
127
128 return devices;
129}
130
131void QDeviceDiscoveryUDev::handleUDevNotification()
132{
133 if (!m_udevMonitor)
134 return;
135
136 struct udev_device *dev;
137 QString devNode;
138
139 dev = udev_monitor_receive_device(udev_monitor: m_udevMonitor);
140 if (!dev)
141 goto cleanup;
142
143 const char *action;
144 action = udev_device_get_action(udev_device: dev);
145 if (!action)
146 goto cleanup;
147
148 const char *str;
149 str = udev_device_get_devnode(udev_device: dev);
150 if (!str)
151 goto cleanup;
152
153 const char *subsystem;
154 devNode = QString::fromUtf8(utf8: str);
155 if (devNode.startsWith(QT_EVDEV_DEVICE ""_L1))
156 subsystem = "input";
157 else if (devNode.startsWith(QT_DRM_DEVICE ""_L1))
158 subsystem = "drm";
159 else goto cleanup;
160
161 // if we cannot determine a type, walk up the device tree
162 if (!checkDeviceType(dev)) {
163 // does not increase the refcount
164 struct udev_device *parent_dev = udev_device_get_parent_with_subsystem_devtype(udev_device: dev, subsystem, devtype: 0);
165 if (!parent_dev)
166 goto cleanup;
167
168 if (!checkDeviceType(dev: parent_dev))
169 goto cleanup;
170 }
171
172 if (qstrcmp(str1: action, str2: "add") == 0)
173 emit deviceDetected(deviceNode: devNode);
174
175 if (qstrcmp(str1: action, str2: "remove") == 0)
176 emit deviceRemoved(deviceNode: devNode);
177
178cleanup:
179 udev_device_unref(udev_device: dev);
180}
181
182bool QDeviceDiscoveryUDev::checkDeviceType(udev_device *dev)
183{
184 if (!dev)
185 return false;
186
187 if ((m_types & Device_Keyboard) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_KEYBOARD"), str2: "1") == 0 )) {
188 const QString capabilities_key = QString::fromUtf8(utf8: udev_device_get_sysattr_value(udev_device: dev, sysattr: "capabilities/key"));
189 const auto val = QStringView{capabilities_key}.split(sep: u' ', behavior: Qt::SkipEmptyParts);
190 if (!val.isEmpty()) {
191 bool ok;
192 unsigned long long keys = val.last().toULongLong(ok: &ok, base: 16);
193 if (ok) {
194 // Tests if the letter Q is valid for the device. We may want to alter this test, but it seems mostly reliable.
195 bool test = (keys >> KEY_Q) & 1;
196 if (test)
197 return true;
198 }
199 }
200 }
201
202 if ((m_types & Device_Keyboard) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_KEY"), str2: "1") == 0 ))
203 return true;
204
205 if ((m_types & Device_Mouse) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_MOUSE"), str2: "1") == 0))
206 return true;
207
208 if ((m_types & Device_Touchpad) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TOUCHPAD"), str2: "1") == 0))
209 return true;
210
211 if ((m_types & Device_Touchscreen) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TOUCHSCREEN"), str2: "1") == 0))
212 return true;
213
214 if ((m_types & Device_Tablet) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TABLET"), str2: "1") == 0))
215 return true;
216
217 if ((m_types & Device_Joystick) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_JOYSTICK"), str2: "1") == 0))
218 return true;
219
220 if ((m_types & Device_DRM) && (qstrcmp(str1: udev_device_get_subsystem(udev_device: dev), str2: "drm") == 0))
221 return true;
222
223 return false;
224}
225
226QT_END_NAMESPACE
227
228#include "moc_qdevicediscovery_udev_p.cpp"
229

source code of qtbase/src/platformsupport/devicediscovery/qdevicediscovery_udev.cpp