diff --git a/clients/upower/battery_status.cpp b/clients/upower/battery_status.cpp index a321499..ccec1cc 100644 --- a/clients/upower/battery_status.cpp +++ b/clients/upower/battery_status.cpp @@ -4,6 +4,7 @@ #include "cutefish/upower/battery_status.h" #include +#include #include "base/event_loop.h" #include "cutefish/upower/device.h" @@ -18,6 +19,9 @@ static constexpr const char* kInterface = "com.cutefish.Services.Battery"; static constexpr const char* kUPowerService = "org.freedesktop.UPower"; static constexpr const char* kUPowerPath = "/org/freedesktop/UPower"; +static constexpr const char* kDisplayDevicePath = "/org/freedesktop/UPower/devices/DisplayDevice"; +// UPower's device type of a battery. +static constexpr int64_t kTypeBattery = 2; std::shared_ptr BatteryStatus::shared() { @@ -38,6 +42,18 @@ BatteryStatus::BatteryStatus(Connection* sessionBus, Connection* systemBus) : m_service(sessionBus, kService, kPath, kInterface) , m_upower(systemBus, kUPowerService, kUPowerPath, kUPowerService) { + if (!sessionBus && systemBus) { + m_displayDevice = std::make_unique(kDisplayDevicePath, systemBus); + m_displayDeviceSubscriptions.push_back( + m_displayDevice->propertiesChanged.connect([this](const VariantMap&) { applyDisplayDevice(); })); + m_displayDeviceSubscriptions.push_back(m_displayDevice->readyChanged.connect([this] { + if (!m_ready) { + m_ready = true; + readyChanged.emit(); + } + })); + } + // The signals only say that something moved; one GetAll brings every value up to date. for (const char* signal : {"chargeStateChanged", "chargePercentChanged", "capacityChanged", "remainingTimeChanged", "lastChargedPercentChanged", "lastChargedSecsChanged"}) @@ -51,7 +67,8 @@ BatteryStatus::BatteryStatus(Connection* sessionBus, Connection* systemBus) fetchOnBattery(); })); - fetchState(); + if (!m_displayDevice) + fetchState(); fetchOnBattery(); } @@ -59,7 +76,8 @@ BatteryStatus::~BatteryStatus() = default; void BatteryStatus::refresh() { - m_service.send("refresh"); + if (m_service.isValid()) + m_service.send("refresh"); } void BatteryStatus::fetchState() @@ -125,6 +143,28 @@ void BatteryStatus::applyState(const VariantMap& properties) } } +void BatteryStatus::applyDisplayDevice() +{ + const VariantMap& properties = m_displayDevice->properties(); + const auto present = properties.find("IsPresent"); + const auto type = properties.find("Type"); + const auto percentage = properties.find("Percentage"); + const bool available = present != properties.end() && present->second.value(false) + && type != properties.end() && integerValue(type->second) == kTypeBattery; + // Rounded as the service rounds it. + const int chargePercent = + percentage == properties.end() ? 0 : int(std::lround(percentage->second.value(0))); + + if (m_chargePercent != chargePercent) { + m_chargePercent = chargePercent; + chargePercentChanged.emit(); + } + if (m_available != available) { + m_available = available; + availableChanged.emit(); + } +} + void BatteryStatus::fetchOnBattery() { m_onBatteryCall = m_upower.getPropertyAsync("OnBattery", [this](const Reply& reply) { diff --git a/clients/upower/battery_status.h b/clients/upower/battery_status.h index abf667c..ac10a2f 100644 --- a/clients/upower/battery_status.h +++ b/clients/upower/battery_status.h @@ -13,6 +13,8 @@ namespace cutefish::upower { +class Device; + // The battery as cutefish-services reports it, and whether the system runs // on it (UPower's OnBattery). Everything is read asynchronously and kept // here, so the getters never block. @@ -23,6 +25,9 @@ public: // holds it. static std::shared_ptr shared(); + // Without a session bus, as at the login screen, the level and whether + // there is a battery come from UPower's display device; the service's + // other figures stay unset. explicit BatteryStatus(base::dbus::Connection* sessionBus = base::dbus::Connection::session(), base::dbus::Connection* systemBus = base::dbus::Connection::system()); ~BatteryStatus(); @@ -62,11 +67,14 @@ public: private: void fetchState(); void applyState(const base::dbus::VariantMap& properties); + void applyDisplayDevice(); void fetchOnBattery(); void setOnBattery(bool onBattery); base::dbus::Proxy m_service; base::dbus::Proxy m_upower; + std::unique_ptr m_displayDevice; + std::vector m_displayDeviceSubscriptions; std::vector m_matches; base::dbus::PendingCall m_stateCall; base::dbus::PendingCall m_onBatteryCall; diff --git a/clients/upower/tests/upower_test.cpp b/clients/upower/tests/upower_test.cpp index 23189fe..8861de8 100644 --- a/clients/upower/tests/upower_test.cpp +++ b/clients/upower/tests/upower_test.cpp @@ -72,6 +72,8 @@ public: std::tuple historyRequest; std::unique_ptr upowerObject; std::unique_ptr deviceObject; + double displayPercentage = 41.6; + std::unique_ptr displayObject; private: void run(std::promise& ready) @@ -133,11 +135,19 @@ private: }); published = device.publish() && published; + displayObject = std::make_unique(connection.get(), "/org/freedesktop/UPower/devices/DisplayDevice", + kDeviceInterface); + displayObject->addProperty("IsPresent", [this] { return m_battery; }); + displayObject->addProperty("Type", [this] { return uint32_t(m_battery ? 2 : 0); }); + displayObject->addProperty("Percentage", [this] { return displayPercentage; }); + published = displayObject->publish() && published; + ready.set_value(published); loop.run(); batteryObject.reset(); upowerObject.reset(); deviceObject.reset(); + displayObject.reset(); } bool m_battery; @@ -247,6 +257,38 @@ TEST_F(UPowerTest, ThePowerSourceFollowsUPower) ASSERT_TRUE(waitFor([&] { return m_services->inService([&] { return m_services->refreshes; }) == 1; })); } +TEST_F(UPowerTest, WithoutASessionBusTheDisplayDeviceIsRead) +{ + startServices(); + BatteryStatus status(nullptr, bus()); + ASSERT_TRUE(waitFor([&] { return status.isReady(); })); + EXPECT_TRUE(status.isAvailable()); + EXPECT_EQ(status.chargePercent(), 42); + EXPECT_EQ(status.statusString(), ""); + + int percent = 0; + Subscription subscription = status.chargePercentChanged.connect([&] { ++percent; }); + m_services->inService([&] { + m_services->displayPercentage = 80.2; + m_services->displayObject->emitPropertiesChanged({"Percentage"}); + }); + ASSERT_TRUE(waitFor([&] { return status.chargePercent() == 80; })); + EXPECT_EQ(percent, 1); + + // Nothing asks a service that is not there. + status.refresh(); + runFor(50ms); + EXPECT_EQ(m_services->inService([&] { return m_services->refreshes; }), 0); +} + +TEST_F(UPowerTest, WithoutASessionBusOrABatteryNothingIsAvailable) +{ + startServices(false); + BatteryStatus status(nullptr, bus()); + ASSERT_TRUE(waitFor([&] { return status.isReady(); })); + EXPECT_FALSE(status.isAvailable()); +} + TEST_F(UPowerTest, SharedStatusIsOneWhileHeld) { std::shared_ptr first = BatteryStatus::shared();