diff --git a/src/services/upower/core.cpp b/src/services/upower/core.cpp index 06cd23ab..750da5c9 100644 --- a/src/services/upower/core.cpp +++ b/src/services/upower/core.cpp @@ -11,7 +11,6 @@ #include <qloggingcategory.h> #include <qobject.h> #include <qqmllist.h> -#include <qtmetamacros.h> #include "../../core/model.hpp" #include "../../dbus/bus.hpp" @@ -53,15 +52,23 @@ UPower::UPower() { } void UPower::init() { + QObject::connect(this->service, &DBusUPowerService::DeviceAdded, this, &UPower::onDeviceAdded); + + QObject::connect( + this->service, + &DBusUPowerService::DeviceRemoved, + this, + &UPower::onDeviceRemoved + ); + this->serviceProperties.setInterface(this->service); this->serviceProperties.updateAllViaGetAll(); - this->registerExisting(); + this->registerDisplayDevice(); + this->registerDevices(); } -void UPower::registerExisting() { - this->registerDevice("/org/freedesktop/UPower/devices/DisplayDevice"); - +void UPower::registerDevices() { auto pending = this->service->EnumerateDevices(); auto* call = new QDBusPendingCallWatcher(pending, this); @@ -82,34 +89,46 @@ void UPower::registerExisting() { QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } +void UPower::registerDisplayDevice() { + auto pending = this->service->GetDisplayDevice(); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply<QDBusObjectPath> reply = *call; + + if (reply.isError()) { + qCWarning(logUPower) << "Failed to get default device:" << reply.error().message(); + } else { + qCDebug(logUPower) << "UPower default device registered at" << reply.value().path(); + this->mDisplayDevice.init(reply.value().path()); + } + + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void UPower::onDeviceAdded(const QDBusObjectPath& path) { this->registerDevice(path.path()); } + +void UPower::onDeviceRemoved(const QDBusObjectPath& path) { + auto iter = this->mDevices.find(path.path()); + + if (iter == this->mDevices.end()) { + qCWarning(logUPower) << "UPower service sent removal signal for" << path.path() + << "which is not registered."; + } else { + auto* device = iter.value(); + this->mDevices.erase(iter); + this->readyDevices.removeObject(device); + qCDebug(logUPower) << "UPowerDevice" << device->path() << "removed."; + } +} + void UPower::onDeviceReady() { auto* device = qobject_cast<UPowerDevice*>(this->sender()); - if (device->path() == "/org/freedesktop/UPower/devices/DisplayDevice") { - this->mDisplayDevice = device; - emit this->displayDeviceChanged(); - qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "ready"; - return; - } - this->readyDevices.insertObject(device); - qCDebug(logUPower) << "UPowerDevice" << device->path() << "ready"; -} - -void UPower::onDeviceDestroyed(QObject* object) { - auto* device = static_cast<UPowerDevice*>(object); // NOLINT - - this->mDevices.remove(device->path()); - - if (device == this->mDisplayDevice) { - this->mDisplayDevice = nullptr; - emit this->displayDeviceChanged(); - qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "destroyed"; - return; - } - - this->readyDevices.removeObject(device); - qCDebug(logUPower) << "UPowerDevice" << device->path() << "destroyed"; } void UPower::registerDevice(const QString& path) { @@ -118,7 +137,9 @@ void UPower::registerDevice(const QString& path) { return; } - auto* device = new UPowerDevice(path, this); + auto* device = new UPowerDevice(this); + device->init(path); + if (!device->isValid()) { qCWarning(logUPower) << "Ignoring invalid UPowerDevice registration of" << path; delete device; @@ -126,13 +147,12 @@ void UPower::registerDevice(const QString& path) { } this->mDevices.insert(path, device); - QObject::connect(device, &UPowerDevice::ready, this, &UPower::onDeviceReady); - QObject::connect(device, &QObject::destroyed, this, &UPower::onDeviceDestroyed); + QObject::connect(device, &UPowerDevice::readyChanged, this, &UPower::onDeviceReady); qCDebug(logUPower) << "Registered UPowerDevice" << path; } -UPowerDevice* UPower::displayDevice() { return this->mDisplayDevice; } +UPowerDevice* UPower::displayDevice() { return &this->mDisplayDevice; } ObjectModel<UPowerDevice>* UPower::devices() { return &this->readyDevices; } @@ -142,12 +162,6 @@ UPower* UPower::instance() { } UPowerQml::UPowerQml(QObject* parent): QObject(parent) { - QObject::connect( - UPower::instance(), - &UPower::displayDeviceChanged, - this, - &UPowerQml::displayDeviceChanged - ); QObject::connect( UPower::instance(), &UPower::onBatteryChanged, diff --git a/src/services/upower/core.hpp b/src/services/upower/core.hpp index 46ff6e20..9ade8121 100644 --- a/src/services/upower/core.hpp +++ b/src/services/upower/core.hpp @@ -1,5 +1,6 @@ #pragma once +#include <qdbusextratypes.h> #include <qdbusservicewatcher.h> #include <qhash.h> #include <qobject.h> @@ -26,21 +27,22 @@ public: static UPower* instance(); signals: - void displayDeviceChanged(); void onBatteryChanged(); private slots: + void onDeviceAdded(const QDBusObjectPath& path); + void onDeviceRemoved(const QDBusObjectPath& path); void onDeviceReady(); - void onDeviceDestroyed(QObject* object); private: explicit UPower(); void init(); - void registerExisting(); + void registerDevices(); + void registerDisplayDevice(); void registerDevice(const QString& path); - UPowerDevice* mDisplayDevice = nullptr; + UPowerDevice mDisplayDevice {this}; QHash<QString, UPowerDevice*> mDevices; ObjectModel<UPowerDevice> readyDevices {this}; @@ -57,11 +59,12 @@ class UPowerQml: public QObject { QML_NAMED_ELEMENT(UPower); QML_SINGLETON; // clang-format off - /// UPower's DisplayDevice for your system. Can be `null`. + /// UPower's DisplayDevice for your system. Cannot be null, + /// but might not be initialized (check @@UPowerDevice.ready if you need to know). /// /// This is an aggregate device and not a physical one, meaning you will not find it in @@devices. /// It is typically the device that is used for displaying information in desktop environments. - Q_PROPERTY(qs::service::upower::UPowerDevice* displayDevice READ displayDevice NOTIFY displayDeviceChanged); + Q_PROPERTY(qs::service::upower::UPowerDevice* displayDevice READ displayDevice CONSTANT); /// All connected UPower devices. QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::upower::UPowerDevice>*); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); @@ -81,7 +84,6 @@ public: } signals: - void displayDeviceChanged(); void onBatteryChanged(); }; diff --git a/src/services/upower/device.cpp b/src/services/upower/device.cpp index a31707d9..d9c6268d 100644 --- a/src/services/upower/device.cpp +++ b/src/services/upower/device.cpp @@ -65,7 +65,15 @@ QString UPowerDeviceType::toString(UPowerDeviceType::Enum type) { } } -UPowerDevice::UPowerDevice(const QString& path, QObject* parent): QObject(parent) { +UPowerDevice::UPowerDevice(QObject* parent): QObject(parent) { + this->bIsLaptopBattery.setBinding([this]() { + return this->bType == UPowerDeviceType::Battery && this->bPowerSupply; + }); + + this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; }); +} + +void UPowerDevice::init(const QString& path) { this->device = new DBusUPowerDevice("org.freedesktop.UPower", path, QDBusConnection::systemBus(), this); @@ -74,26 +82,25 @@ UPowerDevice::UPowerDevice(const QString& path, QObject* parent): QObject(parent return; } - this->bIsLaptopBattery.setBinding([this]() { - return this->bType == UPowerDeviceType::Battery && this->bPowerSupply; - }); - - this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; }); - QObject::connect( &this->deviceProperties, &DBusPropertyGroup::getAllFinished, this, - &UPowerDevice::ready + &UPowerDevice::onGetAllFinished ); this->deviceProperties.setInterface(this->device); this->deviceProperties.updateAllViaGetAll(); } -bool UPowerDevice::isValid() const { return this->device->isValid(); } -QString UPowerDevice::address() const { return this->device->service(); } -QString UPowerDevice::path() const { return this->device->path(); } +bool UPowerDevice::isValid() const { return this->device && this->device->isValid(); } +QString UPowerDevice::address() const { return this->device ? this->device->service() : QString(); } +QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); } + +void UPowerDevice::onGetAllFinished() { + qCDebug(logUPowerDevice) << "UPowerDevice" << device->path() << "ready."; + this->bReady = true; +} } // namespace qs::service::upower diff --git a/src/services/upower/device.hpp b/src/services/upower/device.hpp index 17ce4b47..8d1ac709 100644 --- a/src/services/upower/device.hpp +++ b/src/services/upower/device.hpp @@ -154,12 +154,18 @@ class UPowerDevice: public QObject { Q_PROPERTY(bool isLaptopBattery READ isLaptopBattery NOTIFY isLaptopBatteryChanged BINDABLE bindableIsLaptopBattery); /// Native path of the device specific to your OS. Q_PROPERTY(QString nativePath READ nativePath NOTIFY nativePathChanged BINDABLE bindableNativePath); + /// If device statistics have been queried for this device yet. + /// This will be true for all devices returned from @@UPower.devices, but not the default + /// device, which may be returned before it is ready to avoid returning null. + Q_PROPERTY(bool ready READ ready NOTIFY readyChanged BINDABLE bindableReady); // clang-format on QML_ELEMENT; QML_UNCREATABLE("UPowerDevices can only be acquired from UPower"); public: - explicit UPowerDevice(const QString& path, QObject* parent = nullptr); + explicit UPowerDevice(QObject* parent = nullptr); + + void init(const QString& path); [[nodiscard]] bool isValid() const; [[nodiscard]] QString address() const; @@ -180,9 +186,10 @@ public: QS_BINDABLE_GETTER(QString, bIconName, iconName, bindableIconName); QS_BINDABLE_GETTER(bool, bIsLaptopBattery, isLaptopBattery, bindableIsLaptopBattery); QS_BINDABLE_GETTER(QString, bNativePath, nativePath, bindableNativePath); + QS_BINDABLE_GETTER(bool, bReady, ready, bindableReady); signals: - QSDOC_HIDE void ready(); + QSDOC_HIDE void readyChanged(); void typeChanged(); void powerSupplyChanged(); @@ -200,6 +207,9 @@ signals: void isLaptopBatteryChanged(); void nativePathChanged(); +private slots: + void onGetAllFinished(); + private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, UPowerDeviceType::Enum, bType, &UPowerDevice::typeChanged); @@ -217,6 +227,7 @@ private: Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bIconName, &UPowerDevice::iconNameChanged); Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bIsLaptopBattery, &UPowerDevice::isLaptopBatteryChanged); Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, QString, bNativePath, &UPowerDevice::nativePathChanged); + Q_OBJECT_BINDABLE_PROPERTY(UPowerDevice, bool, bReady, &UPowerDevice::readyChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(UPowerDevice, deviceProperties); QS_DBUS_PROPERTY_BINDING(UPowerDevice, pType, bType, deviceProperties, "Type"); diff --git a/src/services/upower/org.freedesktop.UPower.xml b/src/services/upower/org.freedesktop.UPower.xml index a7522ee5..5ea471f5 100644 --- a/src/services/upower/org.freedesktop.UPower.xml +++ b/src/services/upower/org.freedesktop.UPower.xml @@ -3,5 +3,15 @@ <method name="EnumerateDevices"> <arg direction="out" type="ao" name="devices"/> </method> + <method name="GetDisplayDevice"> + <arg direction="out" type="o" name="devices"/> + </method> + + <signal name="DeviceAdded"> + <arg direction="out" type="o" name="device"/> + </signal> + <signal name="DeviceRemoved"> + <arg direction="out" type="o" name="device"/> + </signal> </interface> </node>