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

source code of bluez-qt/src/device_p.cpp