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
55 QT_BEGIN_NAMESPACE
56
57 Q_LOGGING_CATEGORY(lcDD, "qt.qpa.input")
58
create(QDeviceTypes types,QObject * parent)59 QDeviceDiscovery *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("Failed to get udev library context");
71 }
72
73 return helper;
74 }
75
QDeviceDiscoveryUDev(QDeviceTypes types,struct udev * udev,QObject * parent)76 QDeviceDiscoveryUDev::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(m_udev, "udev");
84 if (!m_udevMonitor) {
85 qWarning("Unable to create an udev monitor. No devices can be detected.");
86 return;
87 }
88
89 udev_monitor_filter_add_match_subsystem_devtype(m_udevMonitor, "input", 0);
90 udev_monitor_filter_add_match_subsystem_devtype(m_udevMonitor, "drm", 0);
91 udev_monitor_enable_receiving(m_udevMonitor);
92 m_udevMonitorFileDescriptor = udev_monitor_get_fd(m_udevMonitor);
93
94 m_udevSocketNotifier = new QSocketNotifier(m_udevMonitorFileDescriptor, QSocketNotifier::Read, this);
95 connect(m_udevSocketNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(handleUDevNotification()));
96 }
97
~QDeviceDiscoveryUDev()98 QDeviceDiscoveryUDev::~QDeviceDiscoveryUDev()
99 {
100 if (m_udevMonitor)
101 udev_monitor_unref(m_udevMonitor);
102
103 if (m_udev)
104 udev_unref(m_udev);
105 }
106
scanConnectedDevices()107 QStringList QDeviceDiscoveryUDev::scanConnectedDevices()
108 {
109 QStringList devices;
110
111 if (!m_udev)
112 return devices;
113
114 udev_enumerate *ue = udev_enumerate_new(m_udev);
115 udev_enumerate_add_match_subsystem(ue, "input");
116 udev_enumerate_add_match_subsystem(ue, "drm");
117
118 if (m_types & Device_Mouse)
119 udev_enumerate_add_match_property(ue, "ID_INPUT_MOUSE", "1");
120 if (m_types & Device_Touchpad)
121 udev_enumerate_add_match_property(ue, "ID_INPUT_TOUCHPAD", "1");
122 if (m_types & Device_Touchscreen)
123 udev_enumerate_add_match_property(ue, "ID_INPUT_TOUCHSCREEN", "1");
124 if (m_types & Device_Keyboard) {
125 udev_enumerate_add_match_property(ue, "ID_INPUT_KEYBOARD", "1");
126 udev_enumerate_add_match_property(ue, "ID_INPUT_KEY", "1");
127 }
128 if (m_types & Device_Tablet)
129 udev_enumerate_add_match_property(ue, "ID_INPUT_TABLET", "1");
130 if (m_types & Device_Joystick)
131 udev_enumerate_add_match_property(ue, "ID_INPUT_JOYSTICK", "1");
132
133 if (udev_enumerate_scan_devices(ue) != 0) {
134 qWarning("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(entry);
141 udev_device *udevice = udev_device_new_from_syspath(m_udev, syspath);
142 QString candidate = QString::fromUtf8(udev_device_get_devnode(udevice));
143 if ((m_types & Device_InputMask) && candidate.startsWith(QLatin1String(QT_EVDEV_DEVICE)))
144 devices << candidate;
145 if ((m_types & Device_VideoMask) && candidate.startsWith(QLatin1String(QT_DRM_DEVICE))) {
146 if (m_types & Device_DRM_PrimaryGPU) {
147 udev_device *pci = udev_device_get_parent_with_subsystem_devtype(udevice, "pci", 0);
148 if (pci) {
149 if (qstrcmp(udev_device_get_sysattr_value(pci, "boot_vga"), "1") == 0)
150 devices << candidate;
151 }
152 } else
153 devices << candidate;
154 }
155
156 udev_device_unref(udevice);
157 }
158 udev_enumerate_unref(ue);
159
160 qCDebug(lcDD) << "Found matching devices" << devices;
161
162 return devices;
163 }
164
handleUDevNotification()165 void 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(m_udevMonitor);
174 if (!dev)
175 goto cleanup;
176
177 const char *action;
178 action = udev_device_get_action(dev);
179 if (!action)
180 goto cleanup;
181
182 const char *str;
183 str = udev_device_get_devnode(dev);
184 if (!str)
185 goto cleanup;
186
187 const char *subsystem;
188 devNode = QString::fromUtf8(str);
189 if (devNode.startsWith(QLatin1String(QT_EVDEV_DEVICE)))
190 subsystem = "input";
191 else if (devNode.startsWith(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(dev, subsystem, 0);
199 if (!parent_dev)
200 goto cleanup;
201
202 if (!checkDeviceType(parent_dev))
203 goto cleanup;
204 }
205
206 if (qstrcmp(action, "add") == 0)
207 emit deviceDetected(devNode);
208
209 if (qstrcmp(action, "remove") == 0)
210 emit deviceRemoved(devNode);
211
212 cleanup:
213 udev_device_unref(dev);
214 }
215
checkDeviceType(udev_device * dev)216 bool QDeviceDiscoveryUDev::checkDeviceType(udev_device *dev)
217 {
218 if (!dev)
219 return false;
220
221 if ((m_types & Device_Keyboard) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"), "1") == 0 )) {
222 const QString capabilities_key = QString::fromUtf8(udev_device_get_sysattr_value(dev, "capabilities/key"));
223 const auto val = capabilities_key.splitRef(QLatin1Char(' '), Qt::SkipEmptyParts);
224 if (!val.isEmpty()) {
225 bool ok;
226 unsigned long long keys = val.last().toULongLong(&ok, 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(udev_device_get_property_value(dev, "ID_INPUT_KEY"), "1") == 0 ))
237 return true;
238
239 if ((m_types & Device_Mouse) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_MOUSE"), "1") == 0))
240 return true;
241
242 if ((m_types & Device_Touchpad) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD"), "1") == 0))
243 return true;
244
245 if ((m_types & Device_Touchscreen) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"), "1") == 0))
246 return true;
247
248 if ((m_types & Device_Tablet) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_TABLET"), "1") == 0))
249 return true;
250
251 if ((m_types & Device_Joystick) && (qstrcmp(udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"), "1") == 0))
252 return true;
253
254 if ((m_types & Device_DRM) && (qstrcmp(udev_device_get_subsystem(dev), "drm") == 0))
255 return true;
256
257 return false;
258 }
259
260 QT_END_NAMESPACE
261