File indexing completed on 2024-03-24 17:00:24

0001 /*
0002  *   SPDX-FileCopyrightText: 2015 David Rosca <nowrep@gmail.com>
0003  *   SPDX-FileCopyrightText: 2021 Nate Graham <nate@kde.org>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "devicemonitor.h"
0009 #include "bluedevil_kded.h"
0010 #include "bluedevildaemon.h"
0011 
0012 #include <QTimer>
0013 
0014 #include <KConfigGroup>
0015 #include <KDirNotify>
0016 #include <KFilePlacesModel>
0017 
0018 #include <BluezQt/Adapter>
0019 #include <BluezQt/Device>
0020 #include <BluezQt/Manager>
0021 #include <BluezQt/Services>
0022 
0023 DeviceMonitor::DeviceMonitor(BlueDevilDaemon *daemon)
0024     : QObject(daemon)
0025     , m_manager(daemon->manager())
0026     , m_isParentValid(true)
0027     , m_config(KSharedConfig::openConfig(QStringLiteral("bluedevilglobalrc")))
0028 {
0029     Q_FOREACH (BluezQt::AdapterPtr adapter, m_manager->adapters()) {
0030         adapterAdded(adapter);
0031     }
0032 
0033     Q_FOREACH (BluezQt::DevicePtr device, m_manager->devices()) {
0034         deviceAdded(device);
0035     }
0036 
0037     connect(m_manager, &BluezQt::Manager::adapterAdded, this, &DeviceMonitor::adapterAdded);
0038     connect(m_manager, &BluezQt::Manager::deviceAdded, this, &DeviceMonitor::deviceAdded);
0039     connect(m_manager, &BluezQt::Manager::bluetoothOperationalChanged, this, &DeviceMonitor::bluetoothOperationalChanged);
0040 
0041     // Catch suspend/resume events so we can save status when suspending and
0042     // resume when waking up
0043     // It's possible that BlueDevilDaemon has been destroyed, but PrepareForSleep is
0044     // received before DeviceMonitor is destroyed, so a crash will happen. To prevent
0045     // the crash, add a check in login1PrepareForSleep to validate BlueDevilDaemon still exists.
0046     connect(
0047         parent(),
0048         &QObject::destroyed,
0049         this,
0050         [this] {
0051             m_isParentValid = false;
0052         },
0053         Qt::DirectConnection);
0054     QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.login1"),
0055                                          QStringLiteral("/org/freedesktop/login1"),
0056                                          QStringLiteral("org.freedesktop.login1.Manager"),
0057                                          QStringLiteral("PrepareForSleep"),
0058                                          this,
0059                                          SLOT(login1PrepareForSleep(bool)));
0060 
0061     // Set initial state
0062     const KConfigGroup globalGroup = m_config->group("Global");
0063     const QString launchState = globalGroup.readEntry("launchState", "remember");
0064     if (launchState == QLatin1String("remember")) {
0065         restoreState();
0066     } else if (launchState == QLatin1String("enable")) {
0067         // Un-block Bluetooth and turn on everything
0068         m_manager->setBluetoothBlocked(false);
0069         for (BluezQt::AdapterPtr adapter : m_manager->adapters()) {
0070             adapter->setPowered(true);
0071         }
0072     } else if (launchState == QLatin1String("disable")) {
0073         // Turn off everything and block Bluetooth
0074         for (BluezQt::AdapterPtr adapter : m_manager->adapters()) {
0075             adapter->setPowered(false);
0076         }
0077         m_manager->setBluetoothBlocked(true);
0078     }
0079 }
0080 
0081 KFilePlacesModel *DeviceMonitor::places()
0082 {
0083     if (!m_places) {
0084         m_places = new KFilePlacesModel(this);
0085     }
0086 
0087     return m_places;
0088 }
0089 
0090 void DeviceMonitor::bluetoothOperationalChanged(bool operational)
0091 {
0092     if (!operational) {
0093         clearPlaces();
0094     }
0095 }
0096 
0097 void DeviceMonitor::adapterAdded(BluezQt::AdapterPtr adapter)
0098 {
0099     // Workaround bluez-qt not registering the powered change after resume from suspend
0100     QTimer::singleShot(1000, this, [this, adapter]() {
0101         restoreAdapter(adapter);
0102     });
0103 }
0104 
0105 void DeviceMonitor::deviceAdded(BluezQt::DevicePtr device)
0106 {
0107     updateDevicePlace(device);
0108     org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("bluetooth:/")));
0109 
0110     connect(device.data(), &BluezQt::Device::connectedChanged, this, &DeviceMonitor::deviceConnectedChanged);
0111 }
0112 
0113 void DeviceMonitor::deviceConnectedChanged(bool connected)
0114 {
0115     Q_UNUSED(connected)
0116     Q_ASSERT(qobject_cast<BluezQt::Device *>(sender()));
0117 
0118     BluezQt::DevicePtr device = static_cast<BluezQt::Device *>(sender())->toSharedPtr();
0119     updateDevicePlace(device);
0120 }
0121 
0122 void DeviceMonitor::login1PrepareForSleep(bool active)
0123 {
0124     if (!m_isParentValid) {
0125         return;
0126     }
0127 
0128     if (active) {
0129         qCDebug(BLUEDEVIL_KDED_LOG) << "About to suspend";
0130         saveState();
0131     } else {
0132         qCDebug(BLUEDEVIL_KDED_LOG) << "About to resume";
0133         restoreState();
0134     }
0135 }
0136 
0137 void DeviceMonitor::saveState()
0138 {
0139     KConfigGroup adaptersGroup = m_config->group("Adapters");
0140     KConfigGroup globalGroup = m_config->group("Global");
0141 
0142     if (m_manager->isBluetoothBlocked()) {
0143         globalGroup.writeEntry<bool>("bluetoothBlocked", true);
0144     } else {
0145         globalGroup.deleteEntry("bluetoothBlocked");
0146 
0147         // Save powered state for each adapter
0148         Q_FOREACH (BluezQt::AdapterPtr adapter, m_manager->adapters()) {
0149             const QString key = QStringLiteral("%1_powered").arg(adapter->address());
0150             adaptersGroup.writeEntry<bool>(key, adapter->isPowered());
0151         }
0152     }
0153 
0154     QStringList connectedDevices;
0155 
0156     Q_FOREACH (BluezQt::DevicePtr device, m_manager->devices()) {
0157         if (device->isConnected()) {
0158             connectedDevices.append(device->address());
0159         }
0160     }
0161 
0162     KConfigGroup devicesGroup = m_config->group("Devices");
0163     devicesGroup.writeEntry<QStringList>(QStringLiteral("connectedDevices"), connectedDevices);
0164 
0165     m_config->sync();
0166 }
0167 
0168 void DeviceMonitor::restoreState()
0169 {
0170     KConfigGroup adaptersGroup = m_config->group("Adapters");
0171     const KConfigGroup globalGroup = m_config->group("Global");
0172 
0173     // Restore blocked/unblocked state
0174     m_manager->setBluetoothBlocked(globalGroup.readEntry<bool>("bluetoothBlocked", false));
0175 
0176     Q_FOREACH (BluezQt::AdapterPtr adapter, m_manager->adapters()) {
0177         const QString key = QStringLiteral("%1_powered").arg(adapter->address());
0178         adapter->setPowered(adaptersGroup.readEntry<bool>(key, true));
0179     }
0180 
0181     KConfigGroup devicesGroup = m_config->group("Devices");
0182     const QStringList &connectedDevices = devicesGroup.readEntry<QStringList>(QStringLiteral("connectedDevices"), QStringList());
0183 
0184     for (const QString &addr : connectedDevices) {
0185         BluezQt::DevicePtr device = m_manager->deviceForAddress(addr);
0186         if (device) {
0187             device->connectToDevice();
0188         }
0189     }
0190 }
0191 
0192 void DeviceMonitor::restoreAdapter(BluezQt::AdapterPtr adapter)
0193 {
0194     KConfigGroup adaptersGroup = m_config->group("Adapters");
0195 
0196     const QString &key = QStringLiteral("%1_powered").arg(adapter->address());
0197     adapter->setPowered(adaptersGroup.readEntry<bool>(key, true));
0198 }
0199 
0200 void DeviceMonitor::clearPlaces()
0201 {
0202     for (int i = 0; i < places()->rowCount(); ++i) {
0203         const QModelIndex &index = places()->index(i, 0);
0204         if (places()->url(index).scheme() == QLatin1String("obexftp")) {
0205             places()->removePlace(index);
0206             i--;
0207         }
0208     }
0209 }
0210 
0211 void DeviceMonitor::updateDevicePlace(BluezQt::DevicePtr device)
0212 {
0213     if (!device->uuids().contains(BluezQt::Services::ObexFileTransfer)) {
0214         return;
0215     }
0216 
0217     QUrl url;
0218     url.setScheme(QStringLiteral("obexftp"));
0219     url.setHost(device->address().replace(QLatin1Char(':'), QLatin1Char('-')));
0220 
0221     const QModelIndex &index = places()->closestItem(url);
0222 
0223     if (device->isConnected()) {
0224         if (places()->url(index) != url) {
0225             qCDebug(BLUEDEVIL_KDED_LOG) << "Adding place" << url;
0226             QString icon = device->icon();
0227             if (icon == QLatin1String("phone")) {
0228                 icon.prepend(QLatin1String("smart")); // Better breeze icon
0229             }
0230             places()->addPlace(device->name(), url, icon);
0231         }
0232     } else {
0233         if (places()->url(index) == url) {
0234             qCDebug(BLUEDEVIL_KDED_LOG) << "Removing place" << url;
0235             places()->removePlace(index);
0236         }
0237     }
0238 }