| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). |
| 4 | ** Contact: http://www.qt-project.org/legal |
| 5 | ** |
| 6 | ** This file is part of the QtSystems module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
| 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 http://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at http://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 2.1 or version 3 as published by the Free |
| 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
| 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
| 22 | ** following information to ensure the GNU Lesser General Public License |
| 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
| 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| 25 | ** |
| 26 | ** As a special exception, The Qt Company gives you certain additional |
| 27 | ** rights. These rights are described in The Qt Company LGPL Exception |
| 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| 29 | ** |
| 30 | ** $QT_END_LICENSE$ |
| 31 | ** |
| 32 | ****************************************************************************/ |
| 33 | |
| 34 | #include "qudevwrapper_p.h" |
| 35 | |
| 36 | #include <QMetaMethod> |
| 37 | #include <QSocketNotifier> |
| 38 | |
| 39 | #include <poll.h> |
| 40 | #include <sys/select.h> |
| 41 | #if !defined(QT_NO_UDEV) |
| 42 | |
| 43 | QT_BEGIN_NAMESPACE |
| 44 | |
| 45 | QUDevWrapper::QUDevWrapper(QObject *parent) |
| 46 | : QObject(parent) |
| 47 | , udev(0) |
| 48 | , udevMonitor(0) |
| 49 | , udevFd(-1) |
| 50 | , notifier(0) |
| 51 | , watcherEnabled(false) |
| 52 | , watchPowerSupply(false) |
| 53 | , watchDrives(false) |
| 54 | { |
| 55 | } |
| 56 | |
| 57 | bool QUDevWrapper::addUDevWatcher(const QByteArray &subsystem) |
| 58 | { |
| 59 | if (!udev) |
| 60 | udev = udev_new(); |
| 61 | |
| 62 | if (udev && !udevMonitor) |
| 63 | udevMonitor = udev_monitor_new_from_netlink(udev, name: "udev" ); |
| 64 | |
| 65 | if (udevMonitor) { |
| 66 | if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor: udevMonitor, subsystem, NULL) >= 0) { |
| 67 | if (!watcherEnabled) { |
| 68 | if (udev_monitor_enable_receiving(udev_monitor: udevMonitor) >= 0) { |
| 69 | udevFd = udev_monitor_get_fd(udev_monitor: udevMonitor); |
| 70 | if (udevFd >= 0) { |
| 71 | notifier = new QSocketNotifier(udevFd, QSocketNotifier::Read, this); |
| 72 | if (connect(sender: notifier, SIGNAL(activated(int)), receiver: this, SLOT(onUDevChanges()))) { |
| 73 | watcherEnabled = true; |
| 74 | return true; |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | } else { |
| 79 | if (udev_monitor_filter_update(udev_monitor: udevMonitor) >= 0) |
| 80 | return true; |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | return false; |
| 85 | } |
| 86 | |
| 87 | bool QUDevWrapper::removeAllUDevWatcher() |
| 88 | { |
| 89 | if (!udev || !udevMonitor) |
| 90 | return true; |
| 91 | |
| 92 | if (udev_monitor_filter_remove(udev_monitor: udevMonitor) >= 0) { |
| 93 | if (udev_monitor_filter_update(udev_monitor: udevMonitor) >= 0) |
| 94 | return true; |
| 95 | } |
| 96 | return false; |
| 97 | } |
| 98 | |
| 99 | void QUDevWrapper::connectNotify(const QMetaMethod &signal) |
| 100 | { |
| 101 | static const QMetaMethod driveChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::driveChanged); |
| 102 | static const QMetaMethod batteryDataChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::batteryDataChanged); |
| 103 | static const QMetaMethod chargerTypeChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::chargerTypeChanged); |
| 104 | |
| 105 | if (!watchDrives && signal == driveChangedSignal) { |
| 106 | if (addUDevWatcher(subsystem: "block" )) |
| 107 | watchDrives = true; |
| 108 | } else if (!watchPowerSupply && (signal == batteryDataChangedSignal |
| 109 | || signal == chargerTypeChangedSignal)) { |
| 110 | if (addUDevWatcher(subsystem: "power_supply" )) |
| 111 | watchPowerSupply = true; |
| 112 | } |
| 113 | } |
| 114 | void QUDevWrapper::disconnectNotify(const QMetaMethod &signal) |
| 115 | { |
| 116 | static const QMetaMethod driveChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::driveChanged); |
| 117 | static const QMetaMethod batteryDataChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::batteryDataChanged); |
| 118 | static const QMetaMethod chargerTypeChangedSignal = QMetaMethod::fromSignal(signal: &QUDevWrapper::chargerTypeChanged); |
| 119 | |
| 120 | if (watchDrives && signal == driveChangedSignal) { |
| 121 | if (removeAllUDevWatcher()) { |
| 122 | watchDrives = false; |
| 123 | if (watchPowerSupply) { |
| 124 | if (!addUDevWatcher(subsystem: "power_supply" )) |
| 125 | watchPowerSupply = false; |
| 126 | } |
| 127 | } |
| 128 | } else if (watchPowerSupply && (signal == batteryDataChangedSignal |
| 129 | || signal == chargerTypeChangedSignal)) { |
| 130 | if (removeAllUDevWatcher()) { |
| 131 | watchPowerSupply = false; |
| 132 | if (watchDrives) { |
| 133 | if (!addUDevWatcher(subsystem: "block" )) |
| 134 | watchDrives = false; |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if (!watchDrives && !watchPowerSupply) { |
| 140 | disconnect(sender: notifier, SIGNAL(activated(int)), receiver: this, SLOT(onUDevChanges())); |
| 141 | udev_monitor_unref(udev_monitor: udevMonitor); |
| 142 | udevMonitor = 0; |
| 143 | udevFd = -1; |
| 144 | watcherEnabled = false; |
| 145 | udev_unref(udev); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | void QUDevWrapper::onUDevChanges() |
| 150 | { |
| 151 | int ret; |
| 152 | struct udev_device *device = 0; |
| 153 | QByteArray subsystem; |
| 154 | QByteArray action; |
| 155 | QByteArray sysname; |
| 156 | struct pollfd pollfds[1]; |
| 157 | |
| 158 | pollfds[0].fd = udevFd; |
| 159 | pollfds[0].events = POLLIN; |
| 160 | pollfds[0].revents = 0; |
| 161 | ret = poll(fds: pollfds, nfds: 1, timeout: -1); |
| 162 | |
| 163 | if ((pollfds[0].revents & POLLIN) && ret == 1) { |
| 164 | device = udev_monitor_receive_device(udev_monitor: udevMonitor); |
| 165 | if (device) { |
| 166 | subsystem = udev_device_get_subsystem(udev_device: device); |
| 167 | action = udev_device_get_action(udev_device: device); |
| 168 | sysname = udev_device_get_sysname(udev_device: device); |
| 169 | |
| 170 | #if defined(QT_SIMULATOR) |
| 171 | if (qstrcmp(subsystem, "block" ) == 0 && qstrcmp(action, "change" ) == 0) |
| 172 | emit driveChanged(); |
| 173 | #endif |
| 174 | if (qstrcmp(str1: subsystem, str2: "block" ) == 0 |
| 175 | && ((qstrcmp(str1: action, str2: "add" ) == 0) || qstrcmp(str1: action, str2: "remove" ) == 0)) { |
| 176 | emit driveChanged(); |
| 177 | } else if (qstrcmp(str1: subsystem, str2: "power_supply" ) == 0) { |
| 178 | int i = -1; |
| 179 | if (sysname.contains(c: "AC" )) { |
| 180 | bool enabled = false; |
| 181 | if (qstrcmp(str1: udev_device_get_sysattr_value(udev_device: device, sysattr: "online" ), str2: "1" ) == 0) |
| 182 | enabled = true; |
| 183 | emit chargerTypeChanged(value: "AC" , enabled); |
| 184 | } else if (sysname.contains(c: "USB" )) { |
| 185 | bool enabled = false; |
| 186 | QByteArray charger(udev_device_get_sysattr_value(udev_device: device, sysattr: "type" )); |
| 187 | if (qstrcmp(str1: udev_device_get_sysattr_value(udev_device: device, sysattr: "present" ), str2: "1" ) == 0) |
| 188 | enabled = true; |
| 189 | emit chargerTypeChanged(value: charger, enabled); |
| 190 | } else if (sysname.contains(c: "BAT" )) { |
| 191 | bool ok; |
| 192 | i = sysname.right(len: 1).toInt(ok: &ok); |
| 193 | if (!ok) |
| 194 | i = -1; |
| 195 | } |
| 196 | |
| 197 | if (i > -1) { |
| 198 | QByteArray status(udev_device_get_sysattr_value(udev_device: device, sysattr: "status" )); |
| 199 | if (!status.isEmpty()) |
| 200 | emit batteryDataChanged(battery: i, attribute: "status" , value: status); |
| 201 | |
| 202 | QByteArray remainingCapacity(udev_device_get_sysattr_value(udev_device: device, sysattr: "charge_now" )); |
| 203 | if (!remainingCapacity.isEmpty()) |
| 204 | emit batteryDataChanged(battery: i, attribute: "charge_now" , value: remainingCapacity); |
| 205 | |
| 206 | QByteArray remainingChargingTime(udev_device_get_sysattr_value(udev_device: device, sysattr: "time_to_full_avg" )); |
| 207 | if (!remainingChargingTime.isEmpty()) |
| 208 | emit batteryDataChanged(battery: i, attribute: "time_to_full_avg" , value: remainingChargingTime); |
| 209 | |
| 210 | QByteArray voltage(udev_device_get_sysattr_value(udev_device: device, sysattr: "voltage_now" )); |
| 211 | if (!voltage.isEmpty()) |
| 212 | emit batteryDataChanged(battery: i, attribute: "voltage_now" , value: voltage); |
| 213 | |
| 214 | QByteArray currentFlow(udev_device_get_sysattr_value(udev_device: device, sysattr: "current_now" )); |
| 215 | if (!currentFlow.isEmpty()) |
| 216 | emit batteryDataChanged(battery: i, attribute: "current_now" , value: currentFlow); |
| 217 | |
| 218 | QByteArray batteryStatus(udev_device_get_sysattr_value(udev_device: device, sysattr: "capacity_level" )); |
| 219 | if (!batteryStatus.isEmpty()) |
| 220 | emit batteryDataChanged(battery: i, attribute: "capacity_level" , value: batteryStatus); |
| 221 | } |
| 222 | } |
| 223 | udev_device_unref(udev_device: device); |
| 224 | } |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | QT_END_NAMESPACE |
| 229 | |
| 230 | #endif // QT_NO_UDEV |
| 231 | |