| 1 | /* |
| 2 | * BluezQt - Asynchronous Bluez wrapper library |
| 3 | * |
| 4 | * SPDX-FileCopyrightText: 2014 David Rosca <nowrep@gmail.com> |
| 5 | * |
| 6 | * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
| 7 | */ |
| 8 | |
| 9 | #include "device_p.h" |
| 10 | #include "device.h" |
| 11 | #include "gattserviceremote_p.h" |
| 12 | #include "gattserviceremote.h" |
| 13 | #include "adapter.h" |
| 14 | #include "battery.h" |
| 15 | #include "battery_p.h" |
| 16 | #include "device.h" |
| 17 | #include "input.h" |
| 18 | #include "input_p.h" |
| 19 | #include "macros.h" |
| 20 | #include "mediaplayer.h" |
| 21 | #include "mediaplayer_p.h" |
| 22 | #include "mediatransport.h" |
| 23 | #include "mediatransport_p.h" |
| 24 | #include "utils.h" |
| 25 | |
| 26 | namespace BluezQt |
| 27 | { |
| 28 | static const qint16 = -32768; // qint16 minimum |
| 29 | |
| 30 | DevicePrivate::DevicePrivate(const QString &path, const QVariantMap &properties, const AdapterPtr &adapter) |
| 31 | : QObject() |
| 32 | , m_dbusProperties(nullptr) |
| 33 | , m_deviceClass(0) |
| 34 | , m_appearance(0) |
| 35 | , m_paired(false) |
| 36 | , m_trusted(false) |
| 37 | , m_blocked(false) |
| 38 | , m_legacyPairing(false) |
| 39 | , m_rssi(INVALID_RSSI) |
| 40 | , m_manufacturerData(ManData()) |
| 41 | , m_servicesResolved(false) |
| 42 | , m_connected(false) |
| 43 | , m_adapter(adapter) |
| 44 | { |
| 45 | m_bluezDevice = new BluezDevice(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); |
| 46 | |
| 47 | init(properties); |
| 48 | } |
| 49 | |
| 50 | static QHash<QString, QByteArray> toByteArrayHash(const QDBusArgument &arg) |
| 51 | { |
| 52 | if (arg.currentType() != QDBusArgument::MapType) { |
| 53 | return {}; |
| 54 | } |
| 55 | |
| 56 | QHash<QString, QByteArray> result; |
| 57 | arg.beginMap(); |
| 58 | while (!arg.atEnd()) { |
| 59 | arg.beginMapEntry(); |
| 60 | QString key; |
| 61 | QDBusVariant value; |
| 62 | arg >> key >> value; |
| 63 | result.insert(key: key.toUpper(), value: value.variant().toByteArray()); |
| 64 | arg.endMapEntry(); |
| 65 | } |
| 66 | arg.endMap(); |
| 67 | return result; |
| 68 | } |
| 69 | |
| 70 | void DevicePrivate::init(const QVariantMap &properties) |
| 71 | { |
| 72 | m_dbusProperties = new DBusProperties(Strings::orgBluez(), m_bluezDevice->path(), DBusConnection::orgBluez(), this); |
| 73 | |
| 74 | // Init properties |
| 75 | m_address = properties.value(QStringLiteral("Address" )).toString(); |
| 76 | m_name = properties.value(QStringLiteral("Name" )).toString(); |
| 77 | m_alias = properties.value(QStringLiteral("Alias" )).toString(); |
| 78 | m_deviceClass = properties.value(QStringLiteral("Class" )).toUInt(); |
| 79 | m_appearance = properties.value(QStringLiteral("Appearance" )).toUInt(); |
| 80 | m_icon = properties.value(QStringLiteral("Icon" )).toString(); |
| 81 | m_paired = properties.value(QStringLiteral("Paired" )).toBool(); |
| 82 | m_trusted = properties.value(QStringLiteral("Trusted" )).toBool(); |
| 83 | m_blocked = properties.value(QStringLiteral("Blocked" )).toBool(); |
| 84 | m_legacyPairing = properties.value(QStringLiteral("LegacyPairing" )).toBool(); |
| 85 | m_rssi = properties.value(QStringLiteral("RSSI" )).toInt(); |
| 86 | m_manufacturerData = variantToManData(value: properties.value(QStringLiteral("ManufacturerData" ))); |
| 87 | m_servicesResolved = properties.value(QStringLiteral("ServicesResolved" )).toBool(); |
| 88 | m_connected = properties.value(QStringLiteral("Connected" )).toBool(); |
| 89 | m_uuids = stringListToUpper(list: properties.value(QStringLiteral("UUIDs" )).toStringList()); |
| 90 | m_modalias = properties.value(QStringLiteral("Modalias" )).toString(); |
| 91 | m_serviceData = toByteArrayHash(arg: properties.value(QStringLiteral("ServiceData" )).value<QDBusArgument>()); |
| 92 | |
| 93 | if (!m_rssi) { |
| 94 | m_rssi = INVALID_RSSI; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | void DevicePrivate::interfacesAdded(const QString &path, const QVariantMapMap &interfaces) |
| 99 | { |
| 100 | bool changed = false; |
| 101 | QVariantMapMap::const_iterator it; |
| 102 | |
| 103 | for (it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) { |
| 104 | if (it.key() == Strings::orgBluezBattery1()) { |
| 105 | m_battery = BatteryPtr(new Battery(path, it.value())); |
| 106 | m_battery->d->q = m_battery.toWeakRef(); |
| 107 | Q_EMIT q.lock()->batteryChanged(battery: m_battery); |
| 108 | changed = true; |
| 109 | } else if (it.key() == Strings::orgBluezInput1()) { |
| 110 | m_input = InputPtr(new Input(path, it.value())); |
| 111 | m_input->d->q = m_input.toWeakRef(); |
| 112 | Q_EMIT q.lock()->inputChanged(input: m_input); |
| 113 | changed = true; |
| 114 | } else if (it.key() == Strings::orgBluezMediaPlayer1()) { |
| 115 | m_mediaPlayer = MediaPlayerPtr(new MediaPlayer(path, it.value())); |
| 116 | m_mediaPlayer->d->q = m_mediaPlayer.toWeakRef(); |
| 117 | Q_EMIT q.lock()->mediaPlayerChanged(mediaPlayer: m_mediaPlayer); |
| 118 | changed = true; |
| 119 | } else if (it.key() == Strings::orgBluezMediaTransport1()) { |
| 120 | m_mediaTransport = MediaTransportPtr(new MediaTransport(path, it.value())); |
| 121 | m_mediaTransport->d->q = m_mediaTransport.toWeakRef(); |
| 122 | Q_EMIT q.lock()->mediaTransportChanged(mediaTransport: m_mediaTransport); |
| 123 | changed = true; |
| 124 | } else if (it.key() == Strings::orgBluezGattService1()) { |
| 125 | addGattService(gattServicePath: path,properties: it.value()); |
| 126 | changed = true; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | for (auto& service : m_services) { |
| 131 | if (path.startsWith(s: service->ubi())) { |
| 132 | service->d->interfacesAdded(path, interfaces); |
| 133 | changed = true; |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | if (changed) { |
| 138 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | void DevicePrivate::interfacesRemoved(const QString &path, const QStringList &interfaces) |
| 143 | { |
| 144 | bool changed = false; |
| 145 | |
| 146 | for (const QString &interface : interfaces) { |
| 147 | if (interface == Strings::orgBluezBattery1() && m_battery && m_battery->d->m_path == path) { |
| 148 | m_battery.clear(); |
| 149 | Q_EMIT q.lock()->batteryChanged(battery: m_battery); |
| 150 | changed = true; |
| 151 | } else if (interface == Strings::orgBluezInput1() && m_input && m_input->d->m_path == path) { |
| 152 | m_input.clear(); |
| 153 | Q_EMIT q.lock()->inputChanged(input: m_input); |
| 154 | changed = true; |
| 155 | } else if (interface == Strings::orgBluezMediaPlayer1() && m_mediaPlayer && m_mediaPlayer->d->m_path == path) { |
| 156 | m_mediaPlayer.clear(); |
| 157 | Q_EMIT q.lock()->mediaPlayerChanged(mediaPlayer: m_mediaPlayer); |
| 158 | changed = true; |
| 159 | } else if (interface == Strings::orgBluezMediaTransport1() && m_mediaTransport && m_mediaTransport->d->m_path == path) { |
| 160 | m_mediaTransport.clear(); |
| 161 | Q_EMIT q.lock()->mediaTransportChanged(mediaTransport: m_mediaTransport); |
| 162 | changed = true; |
| 163 | } else if (interface == Strings::orgBluezGattService1()) { |
| 164 | removeGattService(gattServicePath: path); |
| 165 | changed = true; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | for (auto& service : m_services) { |
| 170 | if (path.startsWith(s: service->ubi())) { |
| 171 | service->d->interfacesRemoved(path,interfaces); |
| 172 | changed = true; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (changed) { |
| 177 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | void DevicePrivate::addGattService(const QString &gattServicePath, const QVariantMap &properties) |
| 182 | { |
| 183 | // Check if we have the right path |
| 184 | if (m_bluezDevice->path() != properties.value(QStringLiteral("Device" )).value<QDBusObjectPath>().path()) { |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | DevicePtr device = DevicePtr(this->q); |
| 189 | |
| 190 | if (!device) { |
| 191 | return; |
| 192 | } |
| 193 | |
| 194 | GattServiceRemotePtr gattService = GattServiceRemotePtr(new GattServiceRemote(gattServicePath, properties, device)); |
| 195 | gattService->d->q = gattService.toWeakRef(); |
| 196 | m_services.append(t: gattService); |
| 197 | |
| 198 | Q_EMIT device->gattServiceAdded(service: gattService); |
| 199 | Q_EMIT device->gattServicesChanged(services: m_services); |
| 200 | |
| 201 | // Connections |
| 202 | connect(sender: gattService.data(),signal: &GattServiceRemote::serviceChanged,context: q.lock().data(),slot: &Device::gattServiceChanged); |
| 203 | } |
| 204 | |
| 205 | void DevicePrivate::removeGattService(const QString &gattServicePath) |
| 206 | { |
| 207 | DevicePtr device = DevicePtr(this->q); |
| 208 | |
| 209 | if (!device) { |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | GattServiceRemotePtr gattService = nullptr; |
| 214 | for (int i=0; i < device->gattServices().size(); ++i) { |
| 215 | if (device->gattServices().at(i)->ubi() == gattServicePath) { |
| 216 | gattService = device->gattServices().at(i); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | if (gattService == nullptr) { |
| 221 | return; |
| 222 | } |
| 223 | |
| 224 | m_services.removeOne(t: gattService); |
| 225 | |
| 226 | Q_EMIT device->gattServiceRemoved(service: gattService); |
| 227 | Q_EMIT device->gattServicesChanged(services: m_services); |
| 228 | |
| 229 | // Connections |
| 230 | disconnect(sender: gattService.data(),signal: &GattServiceRemote::serviceChanged,receiver: q.lock().data(),slot: &Device::gattServiceChanged); |
| 231 | } |
| 232 | |
| 233 | QDBusPendingReply<> DevicePrivate::setDBusProperty(const QString &name, const QVariant &value) |
| 234 | { |
| 235 | return m_dbusProperties->Set(interface_name: Strings::orgBluezDevice1(), property_name: name, value: QDBusVariant(value)); |
| 236 | } |
| 237 | |
| 238 | void DevicePrivate::propertiesChanged(const QString &path, const QString &interface, const QVariantMap &changed, const QStringList &invalidated) |
| 239 | { |
| 240 | if (interface == Strings::orgBluezBattery1() && m_battery) { |
| 241 | m_battery->d->propertiesChanged(interface, changed, invalidated); |
| 242 | } else if (interface == Strings::orgBluezInput1() && m_input) { |
| 243 | m_input->d->propertiesChanged(interface, changed, invalidated); |
| 244 | } else if (interface == Strings::orgBluezMediaPlayer1() && m_mediaPlayer) { |
| 245 | m_mediaPlayer->d->propertiesChanged(interface, changed, invalidated); |
| 246 | } else if ((interface == Strings::orgBluezGattService1()) || (interface == Strings::orgBluezGattCharacteristic1()) || (interface == Strings::orgBluezGattDescriptor1())) { |
| 247 | for (GattServiceRemotePtr service : m_services) { |
| 248 | if (path.startsWith(s: service->ubi())) { |
| 249 | service->d->propertiesChanged(path, interface, changed, invalidated); |
| 250 | return; |
| 251 | } |
| 252 | } |
| 253 | } else if (interface != Strings::orgBluezDevice1()) { |
| 254 | return; |
| 255 | } |
| 256 | |
| 257 | QVariantMap::const_iterator i; |
| 258 | for (i = changed.constBegin(); i != changed.constEnd(); ++i) { |
| 259 | const QVariant &value = i.value(); |
| 260 | const QString &property = i.key(); |
| 261 | |
| 262 | if (property == QLatin1String("Name" )) { |
| 263 | namePropertyChanged(value: value.toString()); |
| 264 | } else if (property == QLatin1String("Address" )) { |
| 265 | addressPropertyChanged(value: value.toString()); |
| 266 | } else if (property == QLatin1String("Alias" )) { |
| 267 | aliasPropertyChanged(value: value.toString()); |
| 268 | } else if (property == QLatin1String("Class" )) { |
| 269 | classPropertyChanged(value: value.toUInt()); |
| 270 | } else if (property == QLatin1String("Appearance" )) { |
| 271 | PROPERTY_CHANGED(m_appearance, toUInt, appearanceChanged); |
| 272 | } else if (property == QLatin1String("Icon" )) { |
| 273 | PROPERTY_CHANGED(m_icon, toString, iconChanged); |
| 274 | } else if (property == QLatin1String("Paired" )) { |
| 275 | PROPERTY_CHANGED(m_paired, toBool, pairedChanged); |
| 276 | } else if (property == QLatin1String("Trusted" )) { |
| 277 | PROPERTY_CHANGED(m_trusted, toBool, trustedChanged); |
| 278 | } else if (property == QLatin1String("Blocked" )) { |
| 279 | PROPERTY_CHANGED(m_blocked, toBool, blockedChanged); |
| 280 | } else if (property == QLatin1String("LegacyPairing" )) { |
| 281 | PROPERTY_CHANGED(m_legacyPairing, toBool, legacyPairingChanged); |
| 282 | } else if (property == QLatin1String("RSSI" )) { |
| 283 | PROPERTY_CHANGED(m_rssi, toInt, rssiChanged); |
| 284 | } else if (property == QLatin1String("ManufacturerData" )) { |
| 285 | PROPERTY_CHANGED2(m_manufacturerData, variantToManData(value), manufacturerDataChanged); |
| 286 | } else if (property == QLatin1String("ServicesResolved" )) { |
| 287 | PROPERTY_CHANGED(m_servicesResolved, toBool, servicesResolvedChanged); |
| 288 | } else if (property == QLatin1String("Connected" )) { |
| 289 | PROPERTY_CHANGED(m_connected, toBool, connectedChanged); |
| 290 | } else if (property == QLatin1String("Modalias" )) { |
| 291 | PROPERTY_CHANGED(m_modalias, toString, modaliasChanged); |
| 292 | } else if (property == QLatin1String("UUIDs" )) { |
| 293 | PROPERTY_CHANGED2(m_uuids, stringListToUpper(value.toStringList()), uuidsChanged); |
| 294 | } else if (property == QLatin1String("ServiceData" )) { |
| 295 | PROPERTY_CHANGED2(m_serviceData, toByteArrayHash(value.value<QDBusArgument>()), serviceDataChanged); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | for (const QString &property : invalidated) { |
| 300 | if (property == QLatin1String("Name" )) { |
| 301 | namePropertyChanged(value: QString()); |
| 302 | } else if (property == QLatin1String("Class" )) { |
| 303 | classPropertyChanged(value: 0); |
| 304 | } else if (property == QLatin1String("Appearance" )) { |
| 305 | PROPERTY_INVALIDATED(m_appearance, 0, appearanceChanged); |
| 306 | } else if (property == QLatin1String("Icon" )) { |
| 307 | PROPERTY_INVALIDATED(m_icon, QString(), iconChanged); |
| 308 | } else if (property == QLatin1String("RSSI" )) { |
| 309 | PROPERTY_INVALIDATED(m_rssi, INVALID_RSSI, rssiChanged); |
| 310 | } else if (property == QLatin1String("ManufacturerData" )) { |
| 311 | QMap<uint16_t,QByteArray> map; |
| 312 | PROPERTY_INVALIDATED(m_manufacturerData, map, manufacturerDataChanged); |
| 313 | } else if (property == QLatin1String("ServicesResolved" )) { |
| 314 | PROPERTY_INVALIDATED(m_servicesResolved, false, servicesResolvedChanged); |
| 315 | } else if (property == QLatin1String("Modalias" )) { |
| 316 | PROPERTY_INVALIDATED(m_modalias, QString(), modaliasChanged); |
| 317 | } else if (property == QLatin1String("UUIDs" )) { |
| 318 | PROPERTY_INVALIDATED(m_uuids, QStringList(), uuidsChanged); |
| 319 | } else if (property == QLatin1String("ServiceData" )) { |
| 320 | PROPERTY_INVALIDATED(m_serviceData, (QHash<QString, QByteArray>()), serviceDataChanged); |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 325 | } |
| 326 | |
| 327 | void DevicePrivate::namePropertyChanged(const QString &value) |
| 328 | { |
| 329 | if (m_name != value) { |
| 330 | m_name = value; |
| 331 | Q_EMIT q.lock()->remoteNameChanged(remoteName: m_name); |
| 332 | Q_EMIT q.lock()->friendlyNameChanged(friendlyName: q.lock()->friendlyName()); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | void DevicePrivate::addressPropertyChanged(const QString &value) |
| 337 | { |
| 338 | if (m_address != value) { |
| 339 | m_address = value; |
| 340 | Q_EMIT q.lock()->addressChanged(address: m_address); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | void DevicePrivate::aliasPropertyChanged(const QString &value) |
| 345 | { |
| 346 | if (m_alias != value) { |
| 347 | m_alias = value; |
| 348 | Q_EMIT q.lock()->nameChanged(name: m_alias); |
| 349 | Q_EMIT q.lock()->friendlyNameChanged(friendlyName: q.lock()->friendlyName()); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | void DevicePrivate::classPropertyChanged(quint32 value) |
| 354 | { |
| 355 | if (m_deviceClass != value) { |
| 356 | m_deviceClass = value; |
| 357 | Q_EMIT q.lock()->deviceClassChanged(deviceClass: m_deviceClass); |
| 358 | Q_EMIT q.lock()->typeChanged(type: q.lock()->type()); |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | void DevicePrivate::serviceDataChanged(const QHash<QString, QByteArray> &value) |
| 363 | { |
| 364 | if (m_serviceData != value) { |
| 365 | m_serviceData = value; |
| 366 | Q_EMIT q.lock()->serviceDataChanged(serviceData: m_serviceData); |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | } // namespace BluezQt |
| 371 | |
| 372 | #include "moc_device_p.cpp" |
| 373 | |