1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include <mozilla/Attributes.h>
9 #include <mozilla/dom/battery/Constants.h>
10 #include "mozilla/GRefPtr.h"
11 #include "mozilla/GUniquePtr.h"
14 #include "mozilla/widget/AsyncDBus.h"
16 using namespace mozilla::widget
;
17 using namespace mozilla::dom::battery
;
19 namespace mozilla::hal_impl
{
22 * This is the declaration of UPowerClient class. This class is listening and
23 * communicating to upower daemon through DBus.
24 * There is no header file because this class shouldn't be public.
28 static UPowerClient
* GetInstance();
30 void BeginListening();
35 double GetRemainingTime();
49 eState_PendingDischarge
53 * Update the currently tracked device.
55 void UpdateTrackedDevices();
58 * Update the battery info.
60 bool GetBatteryInfo();
63 * Watch battery device for status
65 bool AddTrackedDevice(const char* devicePath
);
68 * Callback used by 'DeviceChanged' signal.
70 static void DeviceChanged(GDBusProxy
* aProxy
, gchar
* aSenderName
,
71 gchar
* aSignalName
, GVariant
* aParameters
,
72 UPowerClient
* aListener
);
75 * Callback used by 'PropertiesChanged' signal.
76 * This method is called when the the battery level changes.
77 * (Only with upower >= 0.99)
79 static void DevicePropertiesChanged(GDBusProxy
* aProxy
, gchar
* aSenderName
,
80 gchar
* aSignalName
, GVariant
* aParameters
,
81 UPowerClient
* aListener
);
83 RefPtr
<GCancellable
> mCancellable
;
85 // The DBus proxy object to upower.
86 RefPtr
<GDBusProxy
> mUPowerProxy
;
88 // The path of the tracked device.
89 GUniquePtr
<gchar
> mTrackedDevice
;
91 // The DBusGProxy for the tracked device.
92 RefPtr
<GDBusProxy
> mTrackedDeviceProxy
;
96 double mRemainingTime
;
98 static UPowerClient
* sInstance
;
100 static const guint sDeviceTypeBattery
= 2;
101 static const guint64 kUPowerUnknownRemainingTime
= 0;
105 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
106 * mozilla::hal_impl::DisableBatteryNotifications,
107 * and mozilla::hal_impl::GetCurrentBatteryInformation.
110 void EnableBatteryNotifications() {
111 UPowerClient::GetInstance()->BeginListening();
114 void DisableBatteryNotifications() {
115 UPowerClient::GetInstance()->StopListening();
118 void GetCurrentBatteryInformation(hal::BatteryInformation
* aBatteryInfo
) {
119 UPowerClient
* upowerClient
= UPowerClient::GetInstance();
121 aBatteryInfo
->level() = upowerClient
->GetLevel();
122 aBatteryInfo
->charging() = upowerClient
->IsCharging();
123 aBatteryInfo
->remainingTime() = upowerClient
->GetRemainingTime();
127 * Following is the implementation of UPowerClient.
130 UPowerClient
* UPowerClient::sInstance
= nullptr;
133 UPowerClient
* UPowerClient::GetInstance() {
135 sInstance
= new UPowerClient();
141 UPowerClient::UPowerClient()
142 : mLevel(kDefaultLevel
),
143 mCharging(kDefaultCharging
),
144 mRemainingTime(kDefaultRemainingTime
) {}
146 UPowerClient::~UPowerClient() {
148 !mUPowerProxy
&& !mTrackedDevice
&& !mTrackedDeviceProxy
&& !mCancellable
,
149 "The observers have not been correctly removed! "
150 "(StopListening should have been called)");
153 void UPowerClient::BeginListening() {
154 GUniquePtr
<GError
> error
;
156 mCancellable
= dont_AddRef(g_cancellable_new());
157 CreateDBusProxyForBus(G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
,
158 /* aInterfaceInfo = */ nullptr,
159 "org.freedesktop.UPower", "/org/freedesktop/UPower",
160 "org.freedesktop.UPower", mCancellable
)
162 GetCurrentSerialEventTarget(), __func__
,
163 // It's safe to capture this as we use mCancellable to stop
165 [this](RefPtr
<GDBusProxy
>&& aProxy
) {
166 mUPowerProxy
= std::move(aProxy
);
167 UpdateTrackedDevices();
169 [](GUniquePtr
<GError
>&& aError
) {
170 if (!g_error_matches(aError
.get(), G_IO_ERROR
,
171 G_IO_ERROR_CANCELLED
)) {
173 "Failed to create DBus proxy for org.freedesktop.UPower: "
180 void UPowerClient::StopListening() {
182 g_signal_handlers_disconnect_by_func(mUPowerProxy
, (void*)DeviceChanged
,
186 g_cancellable_cancel(mCancellable
);
187 mCancellable
= nullptr;
190 mTrackedDeviceProxy
= nullptr;
191 mTrackedDevice
= nullptr;
192 mUPowerProxy
= nullptr;
194 // We should now show the default values, not the latest we got.
195 mLevel
= kDefaultLevel
;
196 mCharging
= kDefaultCharging
;
197 mRemainingTime
= kDefaultRemainingTime
;
200 bool UPowerClient::AddTrackedDevice(const char* aDevicePath
) {
201 RefPtr
<GDBusProxy
> proxy
= dont_AddRef(g_dbus_proxy_new_for_bus_sync(
202 G_BUS_TYPE_SYSTEM
, G_DBUS_PROXY_FLAGS_NONE
, nullptr,
203 "org.freedesktop.UPower", aDevicePath
, "org.freedesktop.UPower.Device",
204 mCancellable
, nullptr));
209 RefPtr
<GVariant
> deviceType
=
210 dont_AddRef(g_dbus_proxy_get_cached_property(proxy
, "Type"));
211 if (NS_WARN_IF(!deviceType
||
212 !g_variant_is_of_type(deviceType
, G_VARIANT_TYPE_UINT32
))) {
216 if (g_variant_get_uint32(deviceType
) != sDeviceTypeBattery
) {
220 GUniquePtr
<gchar
> device(g_strdup(aDevicePath
));
221 mTrackedDevice
= std::move(device
);
222 mTrackedDeviceProxy
= std::move(proxy
);
224 if (!GetBatteryInfo()) {
227 hal::NotifyBatteryChange(
228 hal::BatteryInformation(mLevel
, mCharging
, mRemainingTime
));
230 g_signal_connect(mTrackedDeviceProxy
, "g-signal",
231 G_CALLBACK(DevicePropertiesChanged
), this);
235 void UPowerClient::UpdateTrackedDevices() {
236 // Reset the current tracked device:
237 g_signal_handlers_disconnect_by_func(mUPowerProxy
, (void*)DeviceChanged
,
240 mTrackedDevice
= nullptr;
241 mTrackedDeviceProxy
= nullptr;
243 DBusProxyCall(mUPowerProxy
, "EnumerateDevices", nullptr,
244 G_DBUS_CALL_FLAGS_NONE
, -1, mCancellable
)
246 GetCurrentSerialEventTarget(), __func__
,
247 // It's safe to capture this as we use mCancellable to stop
249 [this](RefPtr
<GVariant
>&& aResult
) {
250 RefPtr
<GVariant
> variant
=
251 dont_AddRef(g_variant_get_child_value(aResult
.get(), 0));
252 if (!variant
|| !g_variant_is_of_type(
253 variant
, G_VARIANT_TYPE_OBJECT_PATH_ARRAY
)) {
255 "Failed to enumerate devices of org.freedesktop.UPower: "
257 g_variant_get_type_string(aResult
.get()));
260 gsize num
= g_variant_n_children(variant
);
261 for (gsize i
= 0; i
< num
; i
++) {
262 const char* devicePath
= g_variant_get_string(
263 g_variant_get_child_value(variant
, i
), nullptr);
266 "Failed to enumerate devices of org.freedesktop.UPower: "
267 "missing device?\n");
271 * We are looking for the first device that is a battery.
272 * TODO: we could try to combine more than one battery.
274 if (AddTrackedDevice(devicePath
)) {
278 g_signal_connect(mUPowerProxy
, "g-signal",
279 G_CALLBACK(DeviceChanged
), this);
281 [this](GUniquePtr
<GError
>&& aError
) {
282 if (!g_error_matches(aError
.get(), G_IO_ERROR
,
283 G_IO_ERROR_CANCELLED
)) {
285 "Failed to enumerate devices of org.freedesktop.UPower: %s\n",
288 g_signal_connect(mUPowerProxy
, "g-signal",
289 G_CALLBACK(DeviceChanged
), this);
294 void UPowerClient::DeviceChanged(GDBusProxy
* aProxy
, gchar
* aSenderName
,
295 gchar
* aSignalName
, GVariant
* aParameters
,
296 UPowerClient
* aListener
) {
297 // Added new device. Act only if we're missing any tracked device
298 if (!g_strcmp0(aSignalName
, "DeviceAdded")) {
299 if (aListener
->mTrackedDevice
) {
302 } else if (!g_strcmp0(aSignalName
, "DeviceRemoved")) {
303 if (g_strcmp0(aSenderName
, aListener
->mTrackedDevice
.get())) {
307 aListener
->UpdateTrackedDevices();
311 void UPowerClient::DevicePropertiesChanged(GDBusProxy
* aProxy
,
314 GVariant
* aParameters
,
315 UPowerClient
* aListener
) {
316 if (aListener
->GetBatteryInfo()) {
317 hal::NotifyBatteryChange(hal::BatteryInformation(
318 sInstance
->mLevel
, sInstance
->mCharging
, sInstance
->mRemainingTime
));
322 bool UPowerClient::GetBatteryInfo() {
326 * State values are confusing...
327 * First of all, after looking at upower sources (0.9.13), it seems that
328 * PendingDischarge and PendingCharge are not used.
329 * In addition, FullyCharged and Empty states are not clear because we do not
330 * know if the battery is actually charging or not. Those values come directly
331 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
332 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
333 * related to the level, not to the charging state.
334 * In this code, we are going to assume that Full means charging and Empty
335 * means discharging because if that is not the case, the state should not
336 * last a long time (actually, it should disappear at the following update).
337 * It might be even very hard to see real cases where the state is Empty and
338 * the battery is charging or the state is Full and the battery is discharging
339 * given that plugging/unplugging the battery should have an impact on the
343 if (!mTrackedDeviceProxy
) {
347 RefPtr
<GVariant
> value
= dont_AddRef(
348 g_dbus_proxy_get_cached_property(mTrackedDeviceProxy
, "State"));
349 if (NS_WARN_IF(!value
||
350 !g_variant_is_of_type(value
, G_VARIANT_TYPE_UINT32
))) {
354 switch (g_variant_get_uint32(value
)) {
356 mCharging
= kDefaultCharging
;
358 case eState_FullyCharged
:
361 case eState_Charging
:
362 case eState_PendingCharge
:
365 case eState_Discharging
:
367 case eState_PendingDischarge
:
373 * The battery level might be very close to 100% (like 99%) without
374 * increasing. It seems that upower sets the battery state as 'full' in that
375 * case so we should trust it and not even try to get the value.
381 g_dbus_proxy_get_cached_property(mTrackedDeviceProxy
, "Percentage"));
382 if (NS_WARN_IF(!value
||
383 !g_variant_is_of_type(value
, G_VARIANT_TYPE_DOUBLE
))) {
386 mLevel
= round(g_variant_get_double(value
)) * 0.01;
392 value
= dont_AddRef(g_dbus_proxy_get_cached_property(
393 mTrackedDeviceProxy
, mCharging
? "TimeToFull" : "TimeToEmpty"));
394 if (NS_WARN_IF(!value
||
395 !g_variant_is_of_type(value
, G_VARIANT_TYPE_INT64
))) {
398 mRemainingTime
= g_variant_get_int64(value
);
399 if (mRemainingTime
== kUPowerUnknownRemainingTime
) {
400 mRemainingTime
= kUnknownRemainingTime
;
406 double UPowerClient::GetLevel() { return mLevel
; }
408 bool UPowerClient::IsCharging() { return mCharging
; }
410 double UPowerClient::GetRemainingTime() { return mRemainingTime
; }
412 } // namespace mozilla::hal_impl