1/*
2 * BluezQt - Asynchronous Bluez wrapper library
3 *
4 * SPDX-FileCopyrightText: 2014-2015 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 "manager_p.h"
10#include "adapter.h"
11#include "adapter_p.h"
12#include "debug.h"
13#include "device.h"
14#include "device_p.h"
15#include "manager.h"
16#include "utils.h"
17
18#include <QDBusServiceWatcher>
19
20namespace BluezQt
21{
22ManagerPrivate::ManagerPrivate(Manager *parent)
23 : QObject(parent)
24 , q(parent)
25 , m_dbusObjectManager(nullptr)
26 , m_bluezAgentManager(nullptr)
27 , m_bluezProfileManager(nullptr)
28 , m_initialized(false)
29 , m_bluezRunning(false)
30 , m_loaded(false)
31 , m_adaptersLoaded(false)
32{
33 qDBusRegisterMetaType<DBusManagerStruct>();
34 qRegisterMetaType<QVariantMapMap>(typeName: "QVariantMapMap");
35 qDBusRegisterMetaType<QVariantMapMap>();
36
37 m_rfkill = new Rfkill(this);
38 m_bluetoothBlocked = rfkillBlocked();
39 connect(sender: m_rfkill, signal: &Rfkill::stateChanged, context: this, slot: &ManagerPrivate::rfkillStateChanged);
40
41 connect(sender: q, signal: &Manager::adapterRemoved, context: this, slot: &ManagerPrivate::adapterRemoved);
42}
43
44void ManagerPrivate::init()
45{
46 // Keep an eye on org.bluez service
47 QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(Strings::orgBluez(),
48 DBusConnection::orgBluez(),
49 QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
50 this);
51
52 connect(sender: serviceWatcher, signal: &QDBusServiceWatcher::serviceRegistered, context: this, slot: &ManagerPrivate::serviceRegistered);
53 connect(sender: serviceWatcher, signal: &QDBusServiceWatcher::serviceUnregistered, context: this, slot: &ManagerPrivate::serviceUnregistered);
54
55 // Update the current state of org.bluez service
56 if (!DBusConnection::orgBluez().isConnected()) {
57 Q_EMIT initError(QStringLiteral("DBus system bus is not connected!"));
58 return;
59 }
60
61 QDBusMessage call =
62 QDBusMessage::createMethodCall(destination: Strings::orgFreedesktopDBus(), QStringLiteral("/"), interface: Strings::orgFreedesktopDBus(), QStringLiteral("NameHasOwner"));
63
64 call << Strings::orgBluez();
65
66 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(DBusConnection::orgBluez().asyncCall(message: call));
67 connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &ManagerPrivate::nameHasOwnerFinished);
68
69 DBusConnection::orgBluez().connect(service: Strings::orgBluez(),
70 path: QString(),
71 interface: Strings::orgFreedesktopDBusProperties(),
72 QStringLiteral("PropertiesChanged"),
73 receiver: this,
74 SLOT(propertiesChanged(QString, QVariantMap, QStringList)));
75}
76
77void ManagerPrivate::nameHasOwnerFinished(QDBusPendingCallWatcher *watcher)
78{
79 const QDBusPendingReply<bool> &reply = *watcher;
80 watcher->deleteLater();
81
82 if (reply.isError()) {
83 Q_EMIT initError(errorText: reply.error().message());
84 return;
85 }
86
87 m_bluezRunning = reply.value();
88
89 if (m_bluezRunning) {
90 load();
91 } else {
92 m_initialized = true;
93 Q_EMIT initFinished();
94 }
95}
96
97void ManagerPrivate::load()
98{
99 if (!m_bluezRunning || m_loaded) {
100 return;
101 }
102
103 // Force QDBus to cache owner of org.bluez - this will be the only blocking call on system connection
104 DBusConnection::orgBluez().connect(service: Strings::orgBluez(), QStringLiteral("/"), interface: Strings::orgFreedesktopDBus(), QStringLiteral("Dummy"), receiver: this, SLOT(dummy()));
105
106 m_dbusObjectManager = new DBusObjectManager(Strings::orgBluez(), QStringLiteral("/"), DBusConnection::orgBluez(), this);
107
108 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_dbusObjectManager->GetManagedObjects(), this);
109 connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &ManagerPrivate::getManagedObjectsFinished);
110}
111
112void ManagerPrivate::getManagedObjectsFinished(QDBusPendingCallWatcher *watcher)
113{
114 const QDBusPendingReply<DBusManagerStruct> &reply = *watcher;
115 watcher->deleteLater();
116
117 if (reply.isError()) {
118 Q_EMIT initError(errorText: reply.error().message());
119 return;
120 }
121
122 DBusManagerStruct::const_iterator it;
123 const DBusManagerStruct &managedObjects = reply.value();
124
125 for (it = managedObjects.constBegin(); it != managedObjects.constEnd(); ++it) {
126 const QString &path = it.key().path();
127 const QVariantMapMap &interfaces = it.value();
128
129 interfacesAdded(objectPath: it.key(), interfaces);
130
131 if (interfaces.contains(key: Strings::orgBluezAgentManager1())) {
132 m_bluezAgentManager = new BluezAgentManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this);
133 }
134 if (interfaces.contains(key: Strings::orgBluezProfileManager1())) {
135 m_bluezProfileManager = new BluezProfileManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this);
136 }
137 }
138
139 if (!m_bluezAgentManager) {
140 Q_EMIT initError(QStringLiteral("Cannot find org.bluez.AgentManager1 object!"));
141 return;
142 }
143
144 if (!m_bluezProfileManager) {
145 Q_EMIT initError(QStringLiteral("Cannot find org.bluez.ProfileManager1 object!"));
146 return;
147 }
148
149 connect(sender: m_dbusObjectManager, signal: &DBusObjectManager::InterfacesAdded, context: this, slot: &ManagerPrivate::interfacesAdded);
150 connect(sender: m_dbusObjectManager, signal: &DBusObjectManager::InterfacesRemoved, context: this, slot: &ManagerPrivate::interfacesRemoved);
151
152 m_loaded = true;
153 m_initialized = true;
154
155 Q_EMIT q->operationalChanged(operational: true);
156
157 if (q->isBluetoothOperational()) {
158 Q_EMIT q->bluetoothOperationalChanged(operational: true);
159 }
160
161 Q_EMIT initFinished();
162}
163
164void ManagerPrivate::clear()
165{
166 m_loaded = false;
167
168 // Delete all devices first
169 while (!m_devices.isEmpty()) {
170 DevicePtr device = m_devices.begin().value();
171 m_devices.remove(key: m_devices.begin().key());
172 device->adapter()->d->removeDevice(device);
173 }
174
175 // Delete all adapters
176 while (!m_adapters.isEmpty()) {
177 AdapterPtr adapter = m_adapters.begin().value();
178 m_adapters.remove(key: m_adapters.begin().key());
179 Q_EMIT adapter->adapterRemoved(adapter);
180
181 if (m_adapters.isEmpty()) {
182 Q_EMIT q->allAdaptersRemoved();
183 }
184 }
185
186 // Delete all other objects
187 m_usableAdapter.clear();
188
189 if (m_dbusObjectManager) {
190 m_dbusObjectManager->deleteLater();
191 m_dbusObjectManager = nullptr;
192 }
193
194 if (m_bluezAgentManager) {
195 m_bluezAgentManager->deleteLater();
196 m_bluezAgentManager = nullptr;
197 }
198}
199
200AdapterPtr ManagerPrivate::findUsableAdapter() const
201{
202 for (AdapterPtr adapter : std::as_const(t: m_adapters)) {
203 if (adapter->isPowered()) {
204 return adapter;
205 }
206 }
207 return AdapterPtr();
208}
209
210void ManagerPrivate::serviceRegistered()
211{
212 qCDebug(BLUEZQT) << "BlueZ service registered";
213 m_bluezRunning = true;
214
215 load();
216}
217
218void ManagerPrivate::serviceUnregistered()
219{
220 qCDebug(BLUEZQT) << "BlueZ service unregistered";
221
222 bool wasBtOperational = q->isBluetoothOperational();
223 m_bluezRunning = false;
224
225 if (wasBtOperational) {
226 Q_EMIT q->bluetoothOperationalChanged(operational: false);
227 }
228
229 clear();
230 Q_EMIT q->operationalChanged(operational: false);
231}
232
233void ManagerPrivate::interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces)
234{
235 const QString &path = objectPath.path();
236 QVariantMapMap::const_iterator it;
237
238 for (it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) {
239 if (it.key() == Strings::orgBluezAdapter1()) {
240 addAdapter(adapterPath: path, properties: it.value());
241 } else if (it.key() == Strings::orgBluezDevice1()) {
242 addDevice(devicePath: path, properties: it.value());
243 }
244 }
245
246 for (auto it = m_adapters.cbegin(); it != m_adapters.cend(); ++it) {
247 if (path.startsWith(s: it.value()->ubi())) {
248 it.value()->d->interfacesAdded(path, interfaces);
249 break;
250 }
251 }
252
253 for (auto it = m_devices.cbegin(); it != m_devices.cend(); ++it) {
254 if (path.startsWith(s: it.value()->ubi())) {
255 it.value()->d->interfacesAdded(path, interfaces);
256 break;
257 }
258 }
259}
260
261void ManagerPrivate::interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces)
262{
263 const QString &path = objectPath.path();
264
265 for (const QString &interface : interfaces) {
266 if (interface == Strings::orgBluezAdapter1()) {
267 removeAdapter(adapterPath: path);
268 } else if (interface == Strings::orgBluezDevice1()) {
269 removeDevice(devicePath: path);
270 }
271 }
272
273 for (auto it = m_adapters.cbegin(); it != m_adapters.cend(); ++it) {
274 if (path.startsWith(s: it.value()->ubi())) {
275 it.value()->d->interfacesRemoved(path, interfaces);
276 break;
277 }
278 }
279
280 for (auto it = m_devices.cbegin(); it != m_devices.cend(); ++it) {
281 if (path.startsWith(s: it.value()->ubi())) {
282 it.value()->d->interfacesRemoved(path, interfaces);
283 break;
284 }
285 }
286}
287
288void ManagerPrivate::adapterRemoved(const AdapterPtr &adapter)
289{
290 disconnect(sender: adapter.data(), signal: &Adapter::poweredChanged, receiver: this, slot: &ManagerPrivate::adapterPoweredChanged);
291
292 // Current usable adapter was removed
293 if (adapter == m_usableAdapter) {
294 setUsableAdapter(findUsableAdapter());
295 }
296}
297
298void ManagerPrivate::adapterPoweredChanged(bool powered)
299{
300 Q_ASSERT(qobject_cast<Adapter *>(sender()));
301 AdapterPtr adapter = static_cast<Adapter *>(sender())->toSharedPtr();
302
303 // Current usable adapter was powered off
304 if (m_usableAdapter == adapter && !powered) {
305 setUsableAdapter(findUsableAdapter());
306 }
307
308 // Adapter was powered on, set it as usable
309 if (!m_usableAdapter && powered) {
310 setUsableAdapter(adapter);
311 }
312}
313
314void ManagerPrivate::rfkillStateChanged(Rfkill::State state)
315{
316 Q_UNUSED(state)
317
318 bool blocked = rfkillBlocked();
319 bool wasBtOperational = q->isBluetoothOperational();
320
321 if (m_bluetoothBlocked != blocked) {
322 m_bluetoothBlocked = blocked;
323 Q_EMIT q->bluetoothBlockedChanged(blocked: m_bluetoothBlocked);
324 if (wasBtOperational != q->isBluetoothOperational()) {
325 Q_EMIT q->bluetoothOperationalChanged(operational: q->isBluetoothOperational());
326 }
327 }
328}
329
330void ManagerPrivate::addAdapter(const QString &adapterPath, const QVariantMap &properties)
331{
332 AdapterPtr adapter = AdapterPtr(new Adapter(adapterPath, properties));
333 adapter->d->q = adapter.toWeakRef();
334 m_adapters.insert(key: adapterPath, value: adapter);
335
336 Q_EMIT q->adapterAdded(adapter);
337
338 // Powered adapter was added, set it as usable
339 if (!m_usableAdapter && adapter->isPowered()) {
340 setUsableAdapter(adapter);
341 }
342
343 connect(sender: adapter.data(), signal: &Adapter::deviceAdded, context: q, slot: &Manager::deviceAdded);
344 connect(sender: adapter.data(), signal: &Adapter::adapterRemoved, context: q, slot: &Manager::adapterRemoved);
345 connect(sender: adapter.data(), signal: &Adapter::adapterChanged, context: q, slot: &Manager::adapterChanged);
346 connect(sender: adapter.data(), signal: &Adapter::poweredChanged, context: this, slot: &ManagerPrivate::adapterPoweredChanged);
347}
348
349void ManagerPrivate::addDevice(const QString &devicePath, const QVariantMap &properties)
350{
351 AdapterPtr adapter = m_adapters.value(key: properties.value(QStringLiteral("Adapter")).value<QDBusObjectPath>().path());
352 if (!adapter) {
353 return;
354 }
355
356 DevicePtr device = DevicePtr(new Device(devicePath, properties, adapter));
357 device->d->q = device.toWeakRef();
358 m_devices.insert(key: devicePath, value: device);
359 adapter->d->addDevice(device);
360
361 connect(sender: device.data(), signal: &Device::deviceRemoved, context: q, slot: &Manager::deviceRemoved);
362 connect(sender: device.data(), signal: &Device::deviceChanged, context: q, slot: &Manager::deviceChanged);
363}
364
365void ManagerPrivate::removeAdapter(const QString &adapterPath)
366{
367 AdapterPtr adapter = m_adapters.value(key: adapterPath);
368 if (!adapter) {
369 return;
370 }
371
372 // Make sure we always remove all devices before removing the adapter
373 const auto devices = adapter->devices();
374 for (const DevicePtr &device : devices) {
375 removeDevice(devicePath: device->ubi());
376 }
377
378 m_adapters.remove(key: adapterPath);
379 Q_EMIT adapter->adapterRemoved(adapter);
380
381 if (m_adapters.isEmpty()) {
382 Q_EMIT q->allAdaptersRemoved();
383 }
384
385 disconnect(sender: adapter.data(), signal: &Adapter::adapterChanged, receiver: q, slot: &Manager::adapterChanged);
386 disconnect(sender: adapter.data(), signal: &Adapter::poweredChanged, receiver: this, slot: &ManagerPrivate::adapterPoweredChanged);
387}
388
389void ManagerPrivate::removeDevice(const QString &devicePath)
390{
391 DevicePtr device = m_devices.take(key: devicePath);
392 if (!device) {
393 return;
394 }
395
396 device->adapter()->d->removeDevice(device);
397
398 disconnect(sender: device.data(), signal: &Device::deviceChanged, receiver: q, slot: &Manager::deviceChanged);
399}
400
401bool ManagerPrivate::rfkillBlocked() const
402{
403 return m_rfkill->state() == Rfkill::SoftBlocked || m_rfkill->state() == Rfkill::HardBlocked;
404}
405
406void ManagerPrivate::setUsableAdapter(const AdapterPtr &adapter)
407{
408 if (m_usableAdapter == adapter) {
409 return;
410 }
411
412 qCDebug(BLUEZQT) << "Setting usable adapter" << adapter;
413
414 bool wasBtOperational = q->isBluetoothOperational();
415
416 m_usableAdapter = adapter;
417 Q_EMIT q->usableAdapterChanged(adapter: m_usableAdapter);
418
419 if (wasBtOperational != q->isBluetoothOperational()) {
420 Q_EMIT q->bluetoothOperationalChanged(operational: q->isBluetoothOperational());
421 }
422}
423
424void ManagerPrivate::propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated)
425{
426 // Cut anything after device path to forward it to Device to handle
427 const QString path_full = message().path();
428 const QString path_device = path_full.section(asep: QLatin1Char('/'), astart: 0, aend: 4);
429
430 QTimer::singleShot(interval: 0, receiver: this, slot: [this, interface, changed, invalidated, path_full, path_device]() {
431 AdapterPtr adapter = m_adapters.value(key: path_device);
432 if (adapter) {
433 adapter->d->propertiesChanged(interface, changed, invalidated);
434 return;
435 }
436 DevicePtr device = m_devices.value(key: path_device);
437 if (device) {
438 device->d->propertiesChanged(path: path_full, interface, changed, invalidated);
439 return;
440 }
441 qCDebug(BLUEZQT) << "Unhandled property change" << interface << changed << invalidated;
442 });
443}
444
445void ManagerPrivate::dummy()
446{
447}
448
449} // namespace BluezQt
450
451#include "moc_manager_p.cpp"
452

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