Bug 1869092 - Fix timeouts in browser_PanelMultiView.js. r=twisniewski,test-only
[gecko.git] / hal / linux / UPowerClient.cpp
blobd5f5e1ca52688a785dec28958d7e3d803736a351
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/. */
6 #include "Hal.h"
7 #include "HalLog.h"
8 #include <mozilla/Attributes.h>
9 #include <mozilla/dom/battery/Constants.h>
10 #include "mozilla/GRefPtr.h"
11 #include "mozilla/GUniquePtr.h"
12 #include <cmath>
13 #include <gio/gio.h>
14 #include "mozilla/widget/AsyncDBus.h"
16 using namespace mozilla::widget;
17 using namespace mozilla::dom::battery;
19 namespace mozilla::hal_impl {
21 /**
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.
26 class UPowerClient {
27 public:
28 static UPowerClient* GetInstance();
30 void BeginListening();
31 void StopListening();
33 double GetLevel();
34 bool IsCharging();
35 double GetRemainingTime();
37 ~UPowerClient();
39 private:
40 UPowerClient();
42 enum States {
43 eState_Unknown = 0,
44 eState_Charging,
45 eState_Discharging,
46 eState_Empty,
47 eState_FullyCharged,
48 eState_PendingCharge,
49 eState_PendingDischarge
52 /**
53 * Update the currently tracked device.
55 void UpdateTrackedDevices();
57 /**
58 * Update the battery info.
60 bool GetBatteryInfo();
62 /**
63 * Watch battery device for status
65 bool AddTrackedDevice(const char* devicePath);
67 /**
68 * Callback used by 'DeviceChanged' signal.
70 static void DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName,
71 gchar* aSignalName, GVariant* aParameters,
72 UPowerClient* aListener);
74 /**
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;
94 double mLevel;
95 bool mCharging;
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;
132 /* static */
133 UPowerClient* UPowerClient::GetInstance() {
134 if (!sInstance) {
135 sInstance = new UPowerClient();
138 return sInstance;
141 UPowerClient::UPowerClient()
142 : mLevel(kDefaultLevel),
143 mCharging(kDefaultCharging),
144 mRemainingTime(kDefaultRemainingTime) {}
146 UPowerClient::~UPowerClient() {
147 NS_ASSERTION(
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)
161 ->Then(
162 GetCurrentSerialEventTarget(), __func__,
163 // It's safe to capture this as we use mCancellable to stop
164 // listening.
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)) {
172 g_warning(
173 "Failed to create DBus proxy for org.freedesktop.UPower: "
174 "%s\n",
175 aError->message);
180 void UPowerClient::StopListening() {
181 if (mUPowerProxy) {
182 g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged,
183 this);
185 if (mCancellable) {
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));
205 if (!proxy) {
206 return false;
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))) {
213 return false;
216 if (g_variant_get_uint32(deviceType) != sDeviceTypeBattery) {
217 return false;
220 GUniquePtr<gchar> device(g_strdup(aDevicePath));
221 mTrackedDevice = std::move(device);
222 mTrackedDeviceProxy = std::move(proxy);
224 if (!GetBatteryInfo()) {
225 return false;
227 hal::NotifyBatteryChange(
228 hal::BatteryInformation(mLevel, mCharging, mRemainingTime));
230 g_signal_connect(mTrackedDeviceProxy, "g-signal",
231 G_CALLBACK(DevicePropertiesChanged), this);
232 return true;
235 void UPowerClient::UpdateTrackedDevices() {
236 // Reset the current tracked device:
237 g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged,
238 this);
240 mTrackedDevice = nullptr;
241 mTrackedDeviceProxy = nullptr;
243 DBusProxyCall(mUPowerProxy, "EnumerateDevices", nullptr,
244 G_DBUS_CALL_FLAGS_NONE, -1, mCancellable)
245 ->Then(
246 GetCurrentSerialEventTarget(), __func__,
247 // It's safe to capture this as we use mCancellable to stop
248 // listening.
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)) {
254 g_warning(
255 "Failed to enumerate devices of org.freedesktop.UPower: "
256 "wrong param %s\n",
257 g_variant_get_type_string(aResult.get()));
258 return;
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);
264 if (!devicePath) {
265 g_warning(
266 "Failed to enumerate devices of org.freedesktop.UPower: "
267 "missing device?\n");
268 return;
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)) {
275 break;
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)) {
284 g_warning(
285 "Failed to enumerate devices of org.freedesktop.UPower: %s\n",
286 aError->message);
288 g_signal_connect(mUPowerProxy, "g-signal",
289 G_CALLBACK(DeviceChanged), this);
293 /* static */
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) {
300 return;
302 } else if (!g_strcmp0(aSignalName, "DeviceRemoved")) {
303 if (g_strcmp0(aSenderName, aListener->mTrackedDevice.get())) {
304 return;
307 aListener->UpdateTrackedDevices();
310 /* static */
311 void UPowerClient::DevicePropertiesChanged(GDBusProxy* aProxy,
312 gchar* aSenderName,
313 gchar* aSignalName,
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() {
323 bool isFull = false;
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
340 * level.
343 if (!mTrackedDeviceProxy) {
344 return false;
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))) {
351 return false;
354 switch (g_variant_get_uint32(value)) {
355 case eState_Unknown:
356 mCharging = kDefaultCharging;
357 break;
358 case eState_FullyCharged:
359 isFull = true;
360 [[fallthrough]];
361 case eState_Charging:
362 case eState_PendingCharge:
363 mCharging = true;
364 break;
365 case eState_Discharging:
366 case eState_Empty:
367 case eState_PendingDischarge:
368 mCharging = false;
369 break;
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.
377 if (isFull) {
378 mLevel = 1.0;
379 } else {
380 value = dont_AddRef(
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))) {
384 return false;
386 mLevel = round(g_variant_get_double(value)) * 0.01;
389 if (isFull) {
390 mRemainingTime = 0;
391 } else {
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))) {
396 return false;
398 mRemainingTime = g_variant_get_int64(value);
399 if (mRemainingTime == kUPowerUnknownRemainingTime) {
400 mRemainingTime = kUnknownRemainingTime;
403 return true;
406 double UPowerClient::GetLevel() { return mLevel; }
408 bool UPowerClient::IsCharging() { return mCharging; }
410 double UPowerClient::GetRemainingTime() { return mRemainingTime; }
412 } // namespace mozilla::hal_impl