mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
c576f61eb2
Differential Revision: https://phabricator.services.mozilla.com/D192319
413 lines
13 KiB
C++
413 lines
13 KiB
C++
/* -*- 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
|