You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libcutefish/power/batterystatus.cpp

216 lines
6.5 KiB
C++

// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "batterystatus.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusVariant>
static const QString service = QStringLiteral("com.cutefish.Services");
static const QString path = QStringLiteral("/com/cutefish/Services/Battery");
static const QString interface = QStringLiteral("com.cutefish.Services.Battery");
static const QString upowerService = QStringLiteral("org.freedesktop.UPower");
static const QString upowerPath = QStringLiteral("/org/freedesktop/UPower");
static const QString propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
static BatteryStatus *s_instance = nullptr;
BatteryStatus *BatteryStatus::instance()
{
if (!s_instance)
s_instance = new BatteryStatus;
return s_instance;
}
BatteryStatus::BatteryStatus(QObject *parent)
: QObject(parent)
{
// The signals only say that something moved; one GetAll brings every value up to date.
QDBusConnection bus = QDBusConnection::sessionBus();
for (const char *signal : {"chargeStateChanged", "chargePercentChanged", "capacityChanged",
"remainingTimeChanged", "lastChargedPercentChanged",
"lastChargedSecsChanged"}) {
bus.connect(service, path, interface, QString::fromLatin1(signal), this,
SLOT(fetchState()));
}
QDBusConnection::systemBus().connect(
upowerService, upowerPath, propertiesInterface, QStringLiteral("PropertiesChanged"),
this, SLOT(onUPowerPropertiesChanged(QString, QVariantMap, QStringList)));
fetchState();
fetchOnBattery();
}
bool BatteryStatus::ready() const
{
return m_ready;
}
bool BatteryStatus::available() const
{
return m_available;
}
bool BatteryStatus::onBattery() const
{
return m_onBattery;
}
int BatteryStatus::chargeState() const
{
return m_chargeState;
}
int BatteryStatus::chargePercent() const
{
return m_chargePercent;
}
int BatteryStatus::capacity() const
{
return m_capacity;
}
QString BatteryStatus::statusString() const
{
return m_statusString;
}
int BatteryStatus::lastChargedPercent() const
{
return m_lastChargedPercent;
}
qint64 BatteryStatus::lastChargedSecs() const
{
return m_lastChargedSecs;
}
QString BatteryStatus::udi() const
{
return m_udi;
}
void BatteryStatus::refresh()
{
QDBusConnection::sessionBus().asyncCall(
QDBusMessage::createMethodCall(service, path, interface, QStringLiteral("refresh")));
}
void BatteryStatus::fetchState()
{
QDBusMessage message = QDBusMessage::createMethodCall(service, path, propertiesInterface,
QStringLiteral("GetAll"));
message << interface;
auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message),
this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *call) {
call->deleteLater();
const QDBusPendingReply<QVariantMap> reply = *call;
if (!reply.isError()) {
applyState(reply.value());
if (!m_available) {
m_available = true;
emit availableChanged();
}
}
if (!m_ready) {
m_ready = true;
emit readyChanged();
}
});
}
void BatteryStatus::applyState(const QVariantMap &properties)
{
const int chargeState = properties.value(QStringLiteral("chargeState"), m_chargeState).toInt();
const int chargePercent =
properties.value(QStringLiteral("chargePercent"), m_chargePercent).toInt();
const int capacity = properties.value(QStringLiteral("capacity"), m_capacity).toInt();
const QString statusString =
properties.value(QStringLiteral("statusString"), m_statusString).toString();
const int lastChargedPercent =
properties.value(QStringLiteral("lastChargedPercent"), m_lastChargedPercent).toInt();
const qint64 lastChargedSecs =
properties.value(QStringLiteral("lastChargedSecs"), m_lastChargedSecs).toLongLong();
const QString udi = properties.value(QStringLiteral("udi"), m_udi).toString();
if (m_chargeState != chargeState) {
m_chargeState = chargeState;
emit chargeStateChanged();
}
if (m_chargePercent != chargePercent) {
m_chargePercent = chargePercent;
emit chargePercentChanged();
}
if (m_capacity != capacity) {
m_capacity = capacity;
emit capacityChanged();
}
if (m_statusString != statusString) {
m_statusString = statusString;
emit statusStringChanged();
}
if (m_lastChargedPercent != lastChargedPercent || m_lastChargedSecs != lastChargedSecs) {
m_lastChargedPercent = lastChargedPercent;
m_lastChargedSecs = lastChargedSecs;
emit lastChargedChanged();
}
if (m_udi != udi) {
m_udi = udi;
emit udiChanged();
}
}
void BatteryStatus::fetchOnBattery()
{
QDBusMessage message = QDBusMessage::createMethodCall(upowerService, upowerPath,
propertiesInterface, QStringLiteral("Get"));
message << upowerService << QStringLiteral("OnBattery");
auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::systemBus().asyncCall(message),
this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *call) {
call->deleteLater();
const QDBusPendingReply<QDBusVariant> reply = *call;
if (!reply.isError())
setOnBattery(reply.value().variant().toBool());
});
}
void BatteryStatus::setOnBattery(bool onBattery)
{
if (onBattery == m_onBattery)
return;
m_onBattery = onBattery;
// The service's figures depend on the power source.
refresh();
emit onBatteryChanged();
}
void BatteryStatus::onUPowerPropertiesChanged(const QString &upowerInterface,
const QVariantMap &changed,
const QStringList &invalidated)
{
Q_UNUSED(upowerInterface);
const auto it = changed.constFind(QStringLiteral("OnBattery"));
if (it != changed.cend())
setOnBattery(it->toBool());
else if (invalidated.contains(QStringLiteral("OnBattery")))
fetchOnBattery();
}