1/*
2 SPDX-FileCopyrightText: 2024 Nicolas Fella <nicolas.fella@gmx.de>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7#include "devicesmodel.h"
8
9#include <QDBusPendingReply>
10#include <QDBusServiceWatcher>
11#include <QString>
12
13#include "DaemonDbusInterface.h"
14#include "DeviceDbusInterface.h"
15
16DevicesModel::DevicesModel(QObject *parent)
17 : QAbstractListModel(parent)
18 , m_daemonInterface(
19 new OrgKdeKdeconnectDaemonInterface(QStringLiteral("org.kde.kdeconnect"), QStringLiteral("/modules/kdeconnect"), QDBusConnection::sessionBus(), this))
20{
21 connect(sender: m_daemonInterface, signal: &OrgKdeKdeconnectDaemonInterface::deviceAdded, context: this, slot: &DevicesModel::deviceAdded);
22 connect(sender: m_daemonInterface, signal: &OrgKdeKdeconnectDaemonInterface::deviceVisibilityChanged, context: this, slot: &DevicesModel::deviceUpdated);
23 connect(sender: m_daemonInterface, signal: &OrgKdeKdeconnectDaemonInterface::deviceRemoved, context: this, slot: &DevicesModel::deviceRemoved);
24
25 QDBusServiceWatcher *watcher =
26 new QDBusServiceWatcher(QStringLiteral("org.kde.kdeconnect"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this);
27 connect(sender: watcher, signal: &QDBusServiceWatcher::serviceRegistered, context: this, slot: &DevicesModel::loadDeviceList);
28 connect(sender: watcher, signal: &QDBusServiceWatcher::serviceUnregistered, context: this, slot: &DevicesModel::clearDevices);
29
30 loadDeviceList();
31}
32
33QHash<int, QByteArray> DevicesModel::roleNames() const
34{
35 QHash<int, QByteArray> names = QAbstractItemModel::roleNames();
36 names.insert(key: NameModelRole, value: "name");
37 names.insert(key: IconNameRole, value: "iconName");
38 names.insert(key: IdRole, value: "deviceId");
39 return names;
40}
41
42DevicesModel::~DevicesModel() = default;
43
44int DevicesModel::rowForDevice(const QString &id) const
45{
46 for (int i = 0, c = m_devices.size(); i < c; ++i) {
47 if (m_devices[i].id == id) {
48 return i;
49 }
50 }
51 return -1;
52}
53
54void DevicesModel::deviceAdded(const QString &id)
55{
56 if (rowForDevice(id) >= 0) {
57 Q_ASSERT_X(false, "deviceAdded", "Trying to add a device twice");
58 return;
59 }
60
61 auto dev = std::make_unique<OrgKdeKdeconnectDeviceInterface>(QStringLiteral("org.kde.kdeconnect"),
62 QStringLiteral("/modules/kdeconnect/devices/") + id,
63 args: QDBusConnection::sessionBus(),
64 args: this);
65 Q_ASSERT(dev->isValid());
66
67 if (!dev->isPaired() || !dev->isReachable()) {
68 return;
69 }
70
71 beginInsertRows(parent: QModelIndex(), first: m_devices.size(), last: m_devices.size());
72 addDevice(dev: {.id: id, .interface: std::move(dev)});
73 endInsertRows();
74}
75
76void DevicesModel::deviceRemoved(const QString &id)
77{
78 int row = rowForDevice(id);
79 if (row >= 0) {
80 beginRemoveRows(parent: QModelIndex(), first: row, last: row);
81 m_devices.erase(position: m_devices.begin() + row);
82 endRemoveRows();
83 }
84}
85
86void DevicesModel::deviceUpdated(const QString &id)
87{
88 int row = rowForDevice(id);
89
90 if (row < 0) {
91 deviceAdded(id);
92 } else {
93 auto *dev = m_devices[row].interface.get();
94
95 if (dev->isPaired() && dev->isReachable()) {
96 const QModelIndex idx = index(row);
97 Q_EMIT dataChanged(topLeft: idx, bottomRight: idx);
98 } else {
99 beginRemoveRows(parent: QModelIndex(), first: row, last: row);
100 m_devices.erase(position: m_devices.begin() + row);
101 endRemoveRows();
102 }
103 }
104}
105
106void DevicesModel::loadDeviceList()
107{
108 if (!m_daemonInterface->isValid()) {
109 clearDevices();
110 return;
111 }
112
113 QDBusPendingReply<QStringList> call = m_daemonInterface->devices(onlyReachable: true /*onlyReachable*/, onlyPaired: true /*onlyPaired*/);
114 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
115
116 QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &DevicesModel::gotDeviceList);
117}
118
119void DevicesModel::gotDeviceList(QDBusPendingCallWatcher *watcher)
120{
121 watcher->deleteLater();
122 clearDevices();
123 QDBusPendingReply<QStringList> pendingDeviceIds = *watcher;
124 if (pendingDeviceIds.isError()) {
125 qWarning() << "Error while loading device list" << pendingDeviceIds.error().message();
126 return;
127 }
128
129 Q_ASSERT(m_devices.empty());
130 const QStringList deviceIds = pendingDeviceIds.value();
131
132 if (deviceIds.isEmpty())
133 return;
134
135 beginInsertRows(parent: QModelIndex(), first: 0, last: deviceIds.count() - 1);
136 for (const QString &id : deviceIds) {
137 auto interface = std::make_unique<OrgKdeKdeconnectDeviceInterface>(QStringLiteral("org.kde.kdeconnect"),
138 QStringLiteral("/modules/kdeconnect/devices/") + id,
139 args: QDBusConnection::sessionBus(),
140 args: this);
141
142 if (interface->isPaired() && interface->isReachable()) {
143 addDevice(dev: {.id: id, .interface: std::move(interface)});
144 }
145 }
146 endInsertRows();
147}
148
149void DevicesModel::addDevice(DeviceInterface &&dev)
150{
151 connect(sender: dev.interface.get(), signal: &OrgKdeKdeconnectDeviceInterface::nameChanged, context: this, slot: [this, id = dev.id]() {
152 Q_ASSERT(rowForDevice(id) >= 0);
153 deviceUpdated(id);
154 });
155 m_devices.push_back(x: std::move(dev));
156}
157
158void DevicesModel::clearDevices()
159{
160 beginResetModel();
161 m_devices.clear();
162 endResetModel();
163}
164
165QVariant DevicesModel::data(const QModelIndex &index, int role) const
166{
167 if (!index.isValid() || index.row() < 0 || index.row() >= (int)m_devices.size()) {
168 return QVariant();
169 }
170
171 OrgKdeKdeconnectDeviceInterface *device = m_devices[index.row()].interface.get();
172 Q_ASSERT(device->isValid());
173
174 switch (role) {
175 case NameModelRole:
176 return device->name();
177 case IconNameRole:
178 return device->statusIconName();
179 case IdRole:
180 return m_devices[index.row()].id;
181 default:
182 return QVariant();
183 }
184}
185
186int DevicesModel::rowCount(const QModelIndex &) const
187{
188 return m_devices.size();
189}
190
191#include "moc_devicesmodel.cpp"
192

source code of purpose/src/plugins/kdeconnect/devicesmodel.cpp