1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qdevicediscovery_udev_p.h"
41
42#include <QStringList>
43#include <QCoreApplication>
44#include <QObject>
45#include <QHash>
46#include <QSocketNotifier>
47#include <QLoggingCategory>
48
49#ifdef Q_OS_FREEBSD
50#include <dev/evdev/input.h>
51#else
52#include <linux/input.h>
53#endif
54
55QT_BEGIN_NAMESPACE
56
57Q_LOGGING_CATEGORY(lcDD, "qt.qpa.input")
58
59QDeviceDiscovery *QDeviceDiscovery::create(QDeviceTypes types, QObject *parent)
60{
61 qCDebug(lcDD) << "udev device discovery for type" << types;
62
63 QDeviceDiscovery *helper = 0;
64 struct udev *udev;
65
66 udev = udev_new();
67 if (udev) {
68 helper = new QDeviceDiscoveryUDev(types, udev, parent);
69 } else {
70 qWarning(msg: "Failed to get udev library context");
71 }
72
73 return helper;
74}
75
76QDeviceDiscoveryUDev::QDeviceDiscoveryUDev(QDeviceTypes types, struct udev *udev, QObject *parent) :
77 QDeviceDiscovery(types, parent),
78 m_udev(udev), m_udevMonitor(0), m_udevMonitorFileDescriptor(-1), m_udevSocketNotifier(0)
79{
80 if (!m_udev)
81 return;
82
83 m_udevMonitor = udev_monitor_new_from_netlink(udev: m_udev, name: "udev");
84 if (!m_udevMonitor) {
85 qWarning(msg: "Unable to create an udev monitor. No devices can be detected.");
86 return;
87 }
88
89 udev_monitor_filter_add_match_subsystem_devtype(udev_monitor: m_udevMonitor, subsystem: "input", devtype: 0);
90 udev_monitor_filter_add_match_subsystem_devtype(udev_monitor: m_udevMonitor, subsystem: "drm", devtype: 0);
91 udev_monitor_enable_receiving(udev_monitor: m_udevMonitor);
92 m_udevMonitorFileDescriptor = udev_monitor_get_fd(udev_monitor: m_udevMonitor);
93
94 m_udevSocketNotifier = new QSocketNotifier(m_udevMonitorFileDescriptor, QSocketNotifier::Read, this);
95 connect(sender: m_udevSocketNotifier, SIGNAL(activated(QSocketDescriptor)), receiver: this, SLOT(handleUDevNotification()));
96}
97
98QDeviceDiscoveryUDev::~QDeviceDiscoveryUDev()
99{
100 if (m_udevMonitor)
101 udev_monitor_unref(udev_monitor: m_udevMonitor);
102
103 if (m_udev)
104 udev_unref(udev: m_udev);
105}
106
107QStringList QDeviceDiscoveryUDev::scanConnectedDevices()
108{
109 QStringList devices;
110
111 if (!m_udev)
112 return devices;
113
114 udev_enumerate *ue = udev_enumerate_new(udev: m_udev);
115 udev_enumerate_add_match_subsystem(udev_enumerate: ue, subsystem: "input");
116 udev_enumerate_add_match_subsystem(udev_enumerate: ue, subsystem: "drm");
117
118 if (m_types & Device_Mouse)
119 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_MOUSE", value: "1");
120 if (m_types & Device_Touchpad)
121 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TOUCHPAD", value: "1");
122 if (m_types & Device_Touchscreen)
123 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TOUCHSCREEN", value: "1");
124 if (m_types & Device_Keyboard) {
125 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_KEYBOARD", value: "1");
126 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_KEY", value: "1");
127 }
128 if (m_types & Device_Tablet)
129 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_TABLET", value: "1");
130 if (m_types & Device_Joystick)
131 udev_enumerate_add_match_property(udev_enumerate: ue, property: "ID_INPUT_JOYSTICK", value: "1");
132
133 if (udev_enumerate_scan_devices(udev_enumerate: ue) != 0) {
134 qWarning(msg: "Failed to scan devices");
135 return devices;
136 }
137
138 udev_list_entry *entry;
139 udev_list_entry_foreach (entry, udev_enumerate_get_list_entry(ue)) {
140 const char *syspath = udev_list_entry_get_name(list_entry: entry);
141 udev_device *udevice = udev_device_new_from_syspath(udev: m_udev, syspath);
142 QString candidate = QString::fromUtf8(str: udev_device_get_devnode(udev_device: udevice));
143 if ((m_types & Device_InputMask) && candidate.startsWith(s: QLatin1String(QT_EVDEV_DEVICE)))
144 devices << candidate;
145 if ((m_types & Device_VideoMask) && candidate.startsWith(s: QLatin1String(QT_DRM_DEVICE))) {
146 if (m_types & Device_DRM_PrimaryGPU) {
147 udev_device *pci = udev_device_get_parent_with_subsystem_devtype(udev_device: udevice, subsystem: "pci", devtype: 0);
148 if (pci) {
149 if (qstrcmp(str1: udev_device_get_sysattr_value(udev_device: pci, sysattr: "boot_vga"), str2: "1") == 0)
150 devices << candidate;
151 }
152 } else
153 devices << candidate;
154 }
155
156 udev_device_unref(udev_device: udevice);
157 }
158 udev_enumerate_unref(udev_enumerate: ue);
159
160 qCDebug(lcDD) << "Found matching devices" << devices;
161
162 return devices;
163}
164
165void QDeviceDiscoveryUDev::handleUDevNotification()
166{
167 if (!m_udevMonitor)
168 return;
169
170 struct udev_device *dev;
171 QString devNode;
172
173 dev = udev_monitor_receive_device(udev_monitor: m_udevMonitor);
174 if (!dev)
175 goto cleanup;
176
177 const char *action;
178 action = udev_device_get_action(udev_device: dev);
179 if (!action)
180 goto cleanup;
181
182 const char *str;
183 str = udev_device_get_devnode(udev_device: dev);
184 if (!str)
185 goto cleanup;
186
187 const char *subsystem;
188 devNode = QString::fromUtf8(str);
189 if (devNode.startsWith(s: QLatin1String(QT_EVDEV_DEVICE)))
190 subsystem = "input";
191 else if (devNode.startsWith(s: QLatin1String(QT_DRM_DEVICE)))
192 subsystem = "drm";
193 else goto cleanup;
194
195 // if we cannot determine a type, walk up the device tree
196 if (!checkDeviceType(dev)) {
197 // does not increase the refcount
198 struct udev_device *parent_dev = udev_device_get_parent_with_subsystem_devtype(udev_device: dev, subsystem, devtype: 0);
199 if (!parent_dev)
200 goto cleanup;
201
202 if (!checkDeviceType(dev: parent_dev))
203 goto cleanup;
204 }
205
206 if (qstrcmp(str1: action, str2: "add") == 0)
207 emit deviceDetected(deviceNode: devNode);
208
209 if (qstrcmp(str1: action, str2: "remove") == 0)
210 emit deviceRemoved(deviceNode: devNode);
211
212cleanup:
213 udev_device_unref(udev_device: dev);
214}
215
216bool QDeviceDiscoveryUDev::checkDeviceType(udev_device *dev)
217{
218 if (!dev)
219 return false;
220
221 if ((m_types & Device_Keyboard) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_KEYBOARD"), str2: "1") == 0 )) {
222 const QString capabilities_key = QString::fromUtf8(str: udev_device_get_sysattr_value(udev_device: dev, sysattr: "capabilities/key"));
223 const auto val = capabilities_key.splitRef(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts);
224 if (!val.isEmpty()) {
225 bool ok;
226 unsigned long long keys = val.last().toULongLong(ok: &ok, base: 16);
227 if (ok) {
228 // Tests if the letter Q is valid for the device. We may want to alter this test, but it seems mostly reliable.
229 bool test = (keys >> KEY_Q) & 1;
230 if (test)
231 return true;
232 }
233 }
234 }
235
236 if ((m_types & Device_Keyboard) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_KEY"), str2: "1") == 0 ))
237 return true;
238
239 if ((m_types & Device_Mouse) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_MOUSE"), str2: "1") == 0))
240 return true;
241
242 if ((m_types & Device_Touchpad) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TOUCHPAD"), str2: "1") == 0))
243 return true;
244
245 if ((m_types & Device_Touchscreen) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TOUCHSCREEN"), str2: "1") == 0))
246 return true;
247
248 if ((m_types & Device_Tablet) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_TABLET"), str2: "1") == 0))
249 return true;
250
251 if ((m_types & Device_Joystick) && (qstrcmp(str1: udev_device_get_property_value(udev_device: dev, key: "ID_INPUT_JOYSTICK"), str2: "1") == 0))
252 return true;
253
254 if ((m_types & Device_DRM) && (qstrcmp(str1: udev_device_get_subsystem(udev_device: dev), str2: "drm") == 0))
255 return true;
256
257 return false;
258}
259
260QT_END_NAMESPACE
261

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