Skip to content

Commit 2f194b7

Browse files
committed
service/upower: track device additions/removals
Also ensures displayDevice is always present.
1 parent 611cd76 commit 2f194b7

File tree

5 files changed

+99
-55
lines changed

5 files changed

+99
-55
lines changed

src/services/upower/core.cpp

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <qloggingcategory.h>
1212
#include <qobject.h>
1313
#include <qqmllist.h>
14-
#include <qtmetamacros.h>
1514

1615
#include "../../core/model.hpp"
1716
#include "../../dbus/bus.hpp"
@@ -53,15 +52,23 @@ UPower::UPower() {
5352
}
5453

5554
void UPower::init() {
55+
QObject::connect(this->service, &DBusUPowerService::DeviceAdded, this, &UPower::onDeviceAdded);
56+
57+
QObject::connect(
58+
this->service,
59+
&DBusUPowerService::DeviceRemoved,
60+
this,
61+
&UPower::onDeviceRemoved
62+
);
63+
5664
this->serviceProperties.setInterface(this->service);
5765
this->serviceProperties.updateAllViaGetAll();
5866

59-
this->registerExisting();
67+
this->registerDisplayDevice();
68+
this->registerDevices();
6069
}
6170

62-
void UPower::registerExisting() {
63-
this->registerDevice("/org/freedesktop/UPower/devices/DisplayDevice");
64-
71+
void UPower::registerDevices() {
6572
auto pending = this->service->EnumerateDevices();
6673
auto* call = new QDBusPendingCallWatcher(pending, this);
6774

@@ -82,34 +89,46 @@ void UPower::registerExisting() {
8289
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
8390
}
8491

85-
void UPower::onDeviceReady() {
86-
auto* device = qobject_cast<UPowerDevice*>(this->sender());
92+
void UPower::registerDisplayDevice() {
93+
auto pending = this->service->GetDisplayDevice();
94+
auto* call = new QDBusPendingCallWatcher(pending, this);
8795

88-
if (device->path() == "/org/freedesktop/UPower/devices/DisplayDevice") {
89-
this->mDisplayDevice = device;
90-
emit this->displayDeviceChanged();
91-
qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "ready";
92-
return;
93-
}
96+
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
97+
const QDBusPendingReply<QDBusObjectPath> reply = *call;
9498

95-
this->readyDevices.insertObject(device);
96-
qCDebug(logUPower) << "UPowerDevice" << device->path() << "ready";
99+
if (reply.isError()) {
100+
qCWarning(logUPower) << "Failed to get default device:" << reply.error().message();
101+
} else {
102+
qCDebug(logUPower) << "UPower default device registered at" << reply.value().path();
103+
this->mDisplayDevice.init(reply.value().path());
104+
}
105+
106+
delete call;
107+
};
108+
109+
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
97110
}
98111

99-
void UPower::onDeviceDestroyed(QObject* object) {
100-
auto* device = static_cast<UPowerDevice*>(object); // NOLINT
112+
void UPower::onDeviceAdded(const QDBusObjectPath& path) { this->registerDevice(path.path()); }
101113

102-
this->mDevices.remove(device->path());
114+
void UPower::onDeviceRemoved(const QDBusObjectPath& path) {
115+
auto iter = this->mDevices.find(path.path());
103116

104-
if (device == this->mDisplayDevice) {
105-
this->mDisplayDevice = nullptr;
106-
emit this->displayDeviceChanged();
107-
qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "destroyed";
108-
return;
117+
if (iter == this->mDevices.end()) {
118+
qCWarning(logUPower) << "UPower service sent removal signal for" << path.path()
119+
<< "which is not registered.";
120+
} else {
121+
auto* device = iter.value();
122+
this->mDevices.erase(iter);
123+
this->readyDevices.removeObject(device);
124+
qCDebug(logUPower) << "UPowerDevice" << device->path() << "removed.";
109125
}
126+
}
127+
128+
void UPower::onDeviceReady() {
129+
auto* device = qobject_cast<UPowerDevice*>(this->sender());
110130

111-
this->readyDevices.removeObject(device);
112-
qCDebug(logUPower) << "UPowerDevice" << device->path() << "destroyed";
131+
this->readyDevices.insertObject(device);
113132
}
114133

115134
void UPower::registerDevice(const QString& path) {
@@ -118,21 +137,22 @@ void UPower::registerDevice(const QString& path) {
118137
return;
119138
}
120139

121-
auto* device = new UPowerDevice(path, this);
140+
auto* device = new UPowerDevice(this);
141+
device->init(path);
142+
122143
if (!device->isValid()) {
123144
qCWarning(logUPower) << "Ignoring invalid UPowerDevice registration of" << path;
124145
delete device;
125146
return;
126147
}
127148

128149
this->mDevices.insert(path, device);
129-
QObject::connect(device, &UPowerDevice::ready, this, &UPower::onDeviceReady);
130-
QObject::connect(device, &QObject::destroyed, this, &UPower::onDeviceDestroyed);
150+
QObject::connect(device, &UPowerDevice::readyChanged, this, &UPower::onDeviceReady);
131151

132152
qCDebug(logUPower) << "Registered UPowerDevice" << path;
133153
}
134154

135-
UPowerDevice* UPower::displayDevice() { return this->mDisplayDevice; }
155+
UPowerDevice* UPower::displayDevice() { return &this->mDisplayDevice; }
136156

137157
ObjectModel<UPowerDevice>* UPower::devices() { return &this->readyDevices; }
138158

@@ -142,12 +162,6 @@ UPower* UPower::instance() {
142162
}
143163

144164
UPowerQml::UPowerQml(QObject* parent): QObject(parent) {
145-
QObject::connect(
146-
UPower::instance(),
147-
&UPower::displayDeviceChanged,
148-
this,
149-
&UPowerQml::displayDeviceChanged
150-
);
151165
QObject::connect(
152166
UPower::instance(),
153167
&UPower::onBatteryChanged,

src/services/upower/core.hpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <qdbusextratypes.h>
34
#include <qdbusservicewatcher.h>
45
#include <qhash.h>
56
#include <qobject.h>
@@ -26,21 +27,22 @@ class UPower: public QObject {
2627
static UPower* instance();
2728

2829
signals:
29-
void displayDeviceChanged();
3030
void onBatteryChanged();
3131

3232
private slots:
33+
void onDeviceAdded(const QDBusObjectPath& path);
34+
void onDeviceRemoved(const QDBusObjectPath& path);
3335
void onDeviceReady();
34-
void onDeviceDestroyed(QObject* object);
3536

3637
private:
3738
explicit UPower();
3839

3940
void init();
40-
void registerExisting();
41+
void registerDevices();
42+
void registerDisplayDevice();
4143
void registerDevice(const QString& path);
4244

43-
UPowerDevice* mDisplayDevice = nullptr;
45+
UPowerDevice mDisplayDevice {this};
4446
QHash<QString, UPowerDevice*> mDevices;
4547
ObjectModel<UPowerDevice> readyDevices {this};
4648

@@ -57,11 +59,12 @@ class UPowerQml: public QObject {
5759
QML_NAMED_ELEMENT(UPower);
5860
QML_SINGLETON;
5961
// clang-format off
60-
/// UPower's DisplayDevice for your system. Can be `null`.
62+
/// UPower's DisplayDevice for your system. Cannot be null,
63+
/// but might not be initialized (check @@UPowerDevice.ready if you need to know).
6164
///
6265
/// This is an aggregate device and not a physical one, meaning you will not find it in @@devices.
6366
/// It is typically the device that is used for displaying information in desktop environments.
64-
Q_PROPERTY(qs::service::upower::UPowerDevice* displayDevice READ displayDevice NOTIFY displayDeviceChanged);
67+
Q_PROPERTY(qs::service::upower::UPowerDevice* displayDevice READ displayDevice CONSTANT);
6568
/// All connected UPower devices.
6669
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::upower::UPowerDevice>*);
6770
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
@@ -81,7 +84,6 @@ class UPowerQml: public QObject {
8184
}
8285

8386
signals:
84-
void displayDeviceChanged();
8587
void onBatteryChanged();
8688
};
8789

src/services/upower/device.cpp

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ QString UPowerDeviceType::toString(UPowerDeviceType::Enum type) {
6565
}
6666
}
6767

68-
UPowerDevice::UPowerDevice(const QString& path, QObject* parent): QObject(parent) {
68+
UPowerDevice::UPowerDevice(QObject* parent): QObject(parent) {
69+
this->bIsLaptopBattery.setBinding([this]() {
70+
return this->bType == UPowerDeviceType::Battery && this->bPowerSupply;
71+
});
72+
73+
this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; });
74+
}
75+
76+
void UPowerDevice::init(const QString& path) {
6977
this->device =
7078
new DBusUPowerDevice("org.freedesktop.UPower", path, QDBusConnection::systemBus(), this);
7179

@@ -74,26 +82,25 @@ UPowerDevice::UPowerDevice(const QString& path, QObject* parent): QObject(parent
7482
return;
7583
}
7684

77-
this->bIsLaptopBattery.setBinding([this]() {
78-
return this->bType == UPowerDeviceType::Battery && this->bPowerSupply;
79-
});
80-
81-
this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; });
82-
8385
QObject::connect(
8486
&this->deviceProperties,
8587
&DBusPropertyGroup::getAllFinished,
8688
this,
87-
&UPowerDevice::ready
89+
&UPowerDevice::onGetAllFinished
8890
);
8991

9092
this->deviceProperties.setInterface(this->device);
9193
this->deviceProperties.updateAllViaGetAll();
9294
}
9395

94-
bool UPowerDevice::isValid() const { return this->device->isValid(); }
95-
QString UPowerDevice::address() const { return this->device->service(); }
96-
QString UPowerDevice::path() const { return this->device->path(); }
96+
bool UPowerDevice::isValid() const { return this->device && this->device->isValid(); }
97+
QString UPowerDevice::address() const { return this->device ? this->device->service() : QString(); }
98+
QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); }
99+
100+
void UPowerDevice::onGetAllFinished() {
101+
qCDebug(logUPowerDevice) << "UPowerDevice" << device->path() << "ready.";
102+
this->bReady = true;
103+
}
97104

98105
} // namespace qs::service::upower
99106

src/services/upower/device.hpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,18 @@ class UPowerDevice: public QObject {
154154
Q_PROPERTY(bool isLaptopBattery READ isLaptopBattery NOTIFY isLaptopBatteryChanged BINDABLE bindableIsLaptopBattery);
155155
/// Native path of the device specific to your OS.
156156
Q_PROPERTY(QString nativePath READ nativePath NOTIFY nativePathChanged BINDABLE bindableNativePath);
157+
/// If device statistics have been queried for this device yet.
158+
/// This will be true for all devices returned from @@UPower.devices, but not the default
159+
/// device, which may be returned before it is ready to avoid returning null.
160+
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged BINDABLE bindableReady);
157161
// clang-format on
158162
QML_ELEMENT;
159163
QML_UNCREATABLE("UPowerDevices can only be acquired from UPower");
160164

161165
public:
162-
explicit UPowerDevice(const QString& path, QObject* parent = nullptr);
166+
explicit UPowerDevice(QObject* parent = nullptr);
167+
168+
void init(const QString& path);
163169

164170
[[nodiscard]] bool isValid() const;
165171
[[nodiscard]] QString address() const;
@@ -180,9 +186,10 @@ class UPowerDevice: public QObject {
180186
QS_BINDABLE_GETTER(QString, bIconName, iconName, bindableIconName);
181187
QS_BINDABLE_GETTER(bool, bIsLaptopBattery, isLaptopBattery, bindableIsLaptopBattery);
182188
QS_BINDABLE_GETTER(QString, bNativePath, nativePath, bindableNativePath);
189+
QS_BINDABLE_GETTER(bool, bReady, ready, bindableReady);
183190

184191
signals:
185-
QSDOC_HIDE void ready();
192+
QSDOC_HIDE void readyChanged();
186193

187194
void typeChanged();
188195
void powerSupplyChanged();
@@ -200,6 +207,9 @@ class UPowerDevice: public QObject {
200207
void isLaptopBatteryChanged();
201208
void nativePathChanged();
202209

210+
private slots:
211+
void onGetAllFinished();
212+
203213
private:
204214
// clang-format off
205215
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, UPowerDeviceType::Enum, bType, &UPowerDevice::typeChanged);
@@ -217,6 +227,7 @@ class UPowerDevice: public QObject {
217227
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bIconName, &UPowerDevice::iconNameChanged);
218228
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bIsLaptopBattery, &UPowerDevice::isLaptopBatteryChanged);
219229
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bNativePath, &UPowerDevice::nativePathChanged);
230+
Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bReady, &UPowerDevice::readyChanged);
220231

221232
QS_DBUS_BINDABLE_PROPERTY_GROUP(UPowerDevice, deviceProperties);
222233
QS_DBUS_PROPERTY_BINDING(UPowerDevice, pType, bType, deviceProperties, "Type");

src/services/upower/org.freedesktop.UPower.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,15 @@
33
<method name="EnumerateDevices">
44
<arg direction="out" type="ao" name="devices"/>
55
</method>
6+
<method name="GetDisplayDevice">
7+
<arg direction="out" type="o" name="devices"/>
8+
</method>
9+
10+
<signal name="DeviceAdded">
11+
<arg direction="out" type="o" name="device"/>
12+
</signal>
13+
<signal name="DeviceRemoved">
14+
<arg direction="out" type="o" name="device"/>
15+
</signal>
616
</interface>
717
</node>

0 commit comments

Comments
 (0)