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