summaryrefslogtreecommitdiffstats
path: root/hal/linux/UPowerClient.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /hal/linux/UPowerClient.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'hal/linux/UPowerClient.cpp')
-rw-r--r--hal/linux/UPowerClient.cpp412
1 files changed, 412 insertions, 0 deletions
diff --git a/hal/linux/UPowerClient.cpp b/hal/linux/UPowerClient.cpp
new file mode 100644
index 0000000000..d5f5e1ca52
--- /dev/null
+++ b/hal/linux/UPowerClient.cpp
@@ -0,0 +1,412 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Hal.h"
+#include "HalLog.h"
+#include <mozilla/Attributes.h>
+#include <mozilla/dom/battery/Constants.h>
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include <cmath>
+#include <gio/gio.h>
+#include "mozilla/widget/AsyncDBus.h"
+
+using namespace mozilla::widget;
+using namespace mozilla::dom::battery;
+
+namespace mozilla::hal_impl {
+
+/**
+ * This is the declaration of UPowerClient class. This class is listening and
+ * communicating to upower daemon through DBus.
+ * There is no header file because this class shouldn't be public.
+ */
+class UPowerClient {
+ public:
+ static UPowerClient* GetInstance();
+
+ void BeginListening();
+ void StopListening();
+
+ double GetLevel();
+ bool IsCharging();
+ double GetRemainingTime();
+
+ ~UPowerClient();
+
+ private:
+ UPowerClient();
+
+ enum States {
+ eState_Unknown = 0,
+ eState_Charging,
+ eState_Discharging,
+ eState_Empty,
+ eState_FullyCharged,
+ eState_PendingCharge,
+ eState_PendingDischarge
+ };
+
+ /**
+ * Update the currently tracked device.
+ */
+ void UpdateTrackedDevices();
+
+ /**
+ * Update the battery info.
+ */
+ bool GetBatteryInfo();
+
+ /**
+ * Watch battery device for status
+ */
+ bool AddTrackedDevice(const char* devicePath);
+
+ /**
+ * Callback used by 'DeviceChanged' signal.
+ */
+ static void DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName, GVariant* aParameters,
+ UPowerClient* aListener);
+
+ /**
+ * Callback used by 'PropertiesChanged' signal.
+ * This method is called when the the battery level changes.
+ * (Only with upower >= 0.99)
+ */
+ static void DevicePropertiesChanged(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName, GVariant* aParameters,
+ UPowerClient* aListener);
+
+ RefPtr<GCancellable> mCancellable;
+
+ // The DBus proxy object to upower.
+ RefPtr<GDBusProxy> mUPowerProxy;
+
+ // The path of the tracked device.
+ GUniquePtr<gchar> mTrackedDevice;
+
+ // The DBusGProxy for the tracked device.
+ RefPtr<GDBusProxy> mTrackedDeviceProxy;
+
+ double mLevel;
+ bool mCharging;
+ double mRemainingTime;
+
+ static UPowerClient* sInstance;
+
+ static const guint sDeviceTypeBattery = 2;
+ static const guint64 kUPowerUnknownRemainingTime = 0;
+};
+
+/*
+ * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
+ * mozilla::hal_impl::DisableBatteryNotifications,
+ * and mozilla::hal_impl::GetCurrentBatteryInformation.
+ */
+
+void EnableBatteryNotifications() {
+ UPowerClient::GetInstance()->BeginListening();
+}
+
+void DisableBatteryNotifications() {
+ UPowerClient::GetInstance()->StopListening();
+}
+
+void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) {
+ UPowerClient* upowerClient = UPowerClient::GetInstance();
+
+ aBatteryInfo->level() = upowerClient->GetLevel();
+ aBatteryInfo->charging() = upowerClient->IsCharging();
+ aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime();
+}
+
+/*
+ * Following is the implementation of UPowerClient.
+ */
+
+UPowerClient* UPowerClient::sInstance = nullptr;
+
+/* static */
+UPowerClient* UPowerClient::GetInstance() {
+ if (!sInstance) {
+ sInstance = new UPowerClient();
+ }
+
+ return sInstance;
+}
+
+UPowerClient::UPowerClient()
+ : mLevel(kDefaultLevel),
+ mCharging(kDefaultCharging),
+ mRemainingTime(kDefaultRemainingTime) {}
+
+UPowerClient::~UPowerClient() {
+ NS_ASSERTION(
+ !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy && !mCancellable,
+ "The observers have not been correctly removed! "
+ "(StopListening should have been called)");
+}
+
+void UPowerClient::BeginListening() {
+ GUniquePtr<GError> error;
+
+ mCancellable = dont_AddRef(g_cancellable_new());
+ CreateDBusProxyForBus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE,
+ /* aInterfaceInfo = */ nullptr,
+ "org.freedesktop.UPower", "/org/freedesktop/UPower",
+ "org.freedesktop.UPower", mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // It's safe to capture this as we use mCancellable to stop
+ // listening.
+ [this](RefPtr<GDBusProxy>&& aProxy) {
+ mUPowerProxy = std::move(aProxy);
+ UpdateTrackedDevices();
+ },
+ [](GUniquePtr<GError>&& aError) {
+ if (!g_error_matches(aError.get(), G_IO_ERROR,
+ G_IO_ERROR_CANCELLED)) {
+ g_warning(
+ "Failed to create DBus proxy for org.freedesktop.UPower: "
+ "%s\n",
+ aError->message);
+ }
+ });
+}
+
+void UPowerClient::StopListening() {
+ if (mUPowerProxy) {
+ g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged,
+ this);
+ }
+ if (mCancellable) {
+ g_cancellable_cancel(mCancellable);
+ mCancellable = nullptr;
+ }
+
+ mTrackedDeviceProxy = nullptr;
+ mTrackedDevice = nullptr;
+ mUPowerProxy = nullptr;
+
+ // We should now show the default values, not the latest we got.
+ mLevel = kDefaultLevel;
+ mCharging = kDefaultCharging;
+ mRemainingTime = kDefaultRemainingTime;
+}
+
+bool UPowerClient::AddTrackedDevice(const char* aDevicePath) {
+ RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.UPower", aDevicePath, "org.freedesktop.UPower.Device",
+ mCancellable, nullptr));
+ if (!proxy) {
+ return false;
+ }
+
+ RefPtr<GVariant> deviceType =
+ dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Type"));
+ if (NS_WARN_IF(!deviceType ||
+ !g_variant_is_of_type(deviceType, G_VARIANT_TYPE_UINT32))) {
+ return false;
+ }
+
+ if (g_variant_get_uint32(deviceType) != sDeviceTypeBattery) {
+ return false;
+ }
+
+ GUniquePtr<gchar> device(g_strdup(aDevicePath));
+ mTrackedDevice = std::move(device);
+ mTrackedDeviceProxy = std::move(proxy);
+
+ if (!GetBatteryInfo()) {
+ return false;
+ }
+ hal::NotifyBatteryChange(
+ hal::BatteryInformation(mLevel, mCharging, mRemainingTime));
+
+ g_signal_connect(mTrackedDeviceProxy, "g-signal",
+ G_CALLBACK(DevicePropertiesChanged), this);
+ return true;
+}
+
+void UPowerClient::UpdateTrackedDevices() {
+ // Reset the current tracked device:
+ g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged,
+ this);
+
+ mTrackedDevice = nullptr;
+ mTrackedDeviceProxy = nullptr;
+
+ DBusProxyCall(mUPowerProxy, "EnumerateDevices", nullptr,
+ G_DBUS_CALL_FLAGS_NONE, -1, mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // It's safe to capture this as we use mCancellable to stop
+ // listening.
+ [this](RefPtr<GVariant>&& aResult) {
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_get_child_value(aResult.get(), 0));
+ if (!variant || !g_variant_is_of_type(
+ variant, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) {
+ g_warning(
+ "Failed to enumerate devices of org.freedesktop.UPower: "
+ "wrong param %s\n",
+ g_variant_get_type_string(aResult.get()));
+ return;
+ }
+ gsize num = g_variant_n_children(variant);
+ for (gsize i = 0; i < num; i++) {
+ const char* devicePath = g_variant_get_string(
+ g_variant_get_child_value(variant, i), nullptr);
+ if (!devicePath) {
+ g_warning(
+ "Failed to enumerate devices of org.freedesktop.UPower: "
+ "missing device?\n");
+ return;
+ }
+ /*
+ * We are looking for the first device that is a battery.
+ * TODO: we could try to combine more than one battery.
+ */
+ if (AddTrackedDevice(devicePath)) {
+ break;
+ }
+ }
+ g_signal_connect(mUPowerProxy, "g-signal",
+ G_CALLBACK(DeviceChanged), this);
+ },
+ [this](GUniquePtr<GError>&& aError) {
+ if (!g_error_matches(aError.get(), G_IO_ERROR,
+ G_IO_ERROR_CANCELLED)) {
+ g_warning(
+ "Failed to enumerate devices of org.freedesktop.UPower: %s\n",
+ aError->message);
+ }
+ g_signal_connect(mUPowerProxy, "g-signal",
+ G_CALLBACK(DeviceChanged), this);
+ });
+}
+
+/* static */
+void UPowerClient::DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName, GVariant* aParameters,
+ UPowerClient* aListener) {
+ // Added new device. Act only if we're missing any tracked device
+ if (!g_strcmp0(aSignalName, "DeviceAdded")) {
+ if (aListener->mTrackedDevice) {
+ return;
+ }
+ } else if (!g_strcmp0(aSignalName, "DeviceRemoved")) {
+ if (g_strcmp0(aSenderName, aListener->mTrackedDevice.get())) {
+ return;
+ }
+ }
+ aListener->UpdateTrackedDevices();
+}
+
+/* static */
+void UPowerClient::DevicePropertiesChanged(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ UPowerClient* aListener) {
+ if (aListener->GetBatteryInfo()) {
+ hal::NotifyBatteryChange(hal::BatteryInformation(
+ sInstance->mLevel, sInstance->mCharging, sInstance->mRemainingTime));
+ }
+}
+
+bool UPowerClient::GetBatteryInfo() {
+ bool isFull = false;
+
+ /*
+ * State values are confusing...
+ * First of all, after looking at upower sources (0.9.13), it seems that
+ * PendingDischarge and PendingCharge are not used.
+ * In addition, FullyCharged and Empty states are not clear because we do not
+ * know if the battery is actually charging or not. Those values come directly
+ * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
+ * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
+ * related to the level, not to the charging state.
+ * In this code, we are going to assume that Full means charging and Empty
+ * means discharging because if that is not the case, the state should not
+ * last a long time (actually, it should disappear at the following update).
+ * It might be even very hard to see real cases where the state is Empty and
+ * the battery is charging or the state is Full and the battery is discharging
+ * given that plugging/unplugging the battery should have an impact on the
+ * level.
+ */
+
+ if (!mTrackedDeviceProxy) {
+ return false;
+ }
+
+ RefPtr<GVariant> value = dont_AddRef(
+ g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "State"));
+ if (NS_WARN_IF(!value ||
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32))) {
+ return false;
+ }
+
+ switch (g_variant_get_uint32(value)) {
+ case eState_Unknown:
+ mCharging = kDefaultCharging;
+ break;
+ case eState_FullyCharged:
+ isFull = true;
+ [[fallthrough]];
+ case eState_Charging:
+ case eState_PendingCharge:
+ mCharging = true;
+ break;
+ case eState_Discharging:
+ case eState_Empty:
+ case eState_PendingDischarge:
+ mCharging = false;
+ break;
+ }
+
+ /*
+ * The battery level might be very close to 100% (like 99%) without
+ * increasing. It seems that upower sets the battery state as 'full' in that
+ * case so we should trust it and not even try to get the value.
+ */
+ if (isFull) {
+ mLevel = 1.0;
+ } else {
+ value = dont_AddRef(
+ g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "Percentage"));
+ if (NS_WARN_IF(!value ||
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE))) {
+ return false;
+ }
+ mLevel = round(g_variant_get_double(value)) * 0.01;
+ }
+
+ if (isFull) {
+ mRemainingTime = 0;
+ } else {
+ value = dont_AddRef(g_dbus_proxy_get_cached_property(
+ mTrackedDeviceProxy, mCharging ? "TimeToFull" : "TimeToEmpty"));
+ if (NS_WARN_IF(!value ||
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_INT64))) {
+ return false;
+ }
+ mRemainingTime = g_variant_get_int64(value);
+ if (mRemainingTime == kUPowerUnknownRemainingTime) {
+ mRemainingTime = kUnknownRemainingTime;
+ }
+ }
+ return true;
+}
+
+double UPowerClient::GetLevel() { return mLevel; }
+
+bool UPowerClient::IsCharging() { return mCharging; }
+
+double UPowerClient::GetRemainingTime() { return mRemainingTime; }
+
+} // namespace mozilla::hal_impl