Bug 1835710 - Cancel off-thread JIT compilation before changing nursery allocation...
[gecko.git] / hal / linux / UPowerClient.cpp
blob1de38ad201ea5985c49ad26ed6ac8711d63b44d8
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 <dbus/dbus-glib.h>
9 #include <dbus/dbus-glib-lowlevel.h>
10 #include <mozilla/Attributes.h>
11 #include <mozilla/dom/battery/Constants.h>
12 #include "mozilla/GRefPtr.h"
13 #include "mozilla/GUniquePtr.h"
14 #include <cmath>
16 using namespace mozilla::dom::battery;
18 namespace mozilla::hal_impl {
20 /**
21 * This is the declaration of UPowerClient class. This class is listening and
22 * communicating to upower daemon through DBus.
23 * There is no header file because this class shouldn't be public.
25 class UPowerClient {
26 public:
27 static UPowerClient* GetInstance();
29 void BeginListening();
30 void StopListening();
32 double GetLevel();
33 bool IsCharging();
34 double GetRemainingTime();
36 ~UPowerClient();
38 private:
39 UPowerClient();
41 enum States {
42 eState_Unknown = 0,
43 eState_Charging,
44 eState_Discharging,
45 eState_Empty,
46 eState_FullyCharged,
47 eState_PendingCharge,
48 eState_PendingDischarge
51 /**
52 * Update the currently tracked device.
53 * @return whether everything went ok.
55 void UpdateTrackedDeviceSync();
57 /**
58 * Returns a hash table with the properties of aDevice.
60 already_AddRefed<GHashTable> GetDevicePropertiesSync(DBusGProxy* aProxy);
61 void GetDevicePropertiesAsync(DBusGProxy* aProxy);
62 static void GetDevicePropertiesCallback(DBusGProxy* aProxy,
63 DBusGProxyCall* aCall, void* aData);
65 /**
66 * Using the device properties (aHashTable), this method updates the member
67 * variable storing the values we care about.
69 void UpdateSavedInfo(GHashTable* aHashTable);
71 /**
72 * Callback used by 'DeviceChanged' signal.
74 static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
75 UPowerClient* aListener);
77 /**
78 * Callback used by 'PropertiesChanged' signal.
79 * This method is called when the the battery level changes.
80 * (Only with upower >= 0.99)
82 static void PropertiesChanged(DBusGProxy* aProxy, const gchar*, GHashTable*,
83 char**, UPowerClient* aListener);
85 /**
86 * Callback called when mDBusConnection gets a signal.
88 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
89 DBusMessage* aMessage,
90 void* aData);
92 // The DBus connection object.
93 RefPtr<DBusGConnection> mDBusConnection;
95 // The DBus proxy object to upower.
96 RefPtr<DBusGProxy> mUPowerProxy;
98 // The path of the tracked device.
99 GUniquePtr<gchar> mTrackedDevice;
101 // The DBusGProxy for the tracked device.
102 RefPtr<DBusGProxy> mTrackedDeviceProxy;
104 double mLevel;
105 bool mCharging;
106 double mRemainingTime;
108 static UPowerClient* sInstance;
110 static const guint sDeviceTypeBattery = 2;
111 static const guint64 kUPowerUnknownRemainingTime = 0;
115 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
116 * mozilla::hal_impl::DisableBatteryNotifications,
117 * and mozilla::hal_impl::GetCurrentBatteryInformation.
120 void EnableBatteryNotifications() {
121 UPowerClient::GetInstance()->BeginListening();
124 void DisableBatteryNotifications() {
125 UPowerClient::GetInstance()->StopListening();
128 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) {
129 UPowerClient* upowerClient = UPowerClient::GetInstance();
131 aBatteryInfo->level() = upowerClient->GetLevel();
132 aBatteryInfo->charging() = upowerClient->IsCharging();
133 aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime();
137 * Following is the implementation of UPowerClient.
140 UPowerClient* UPowerClient::sInstance = nullptr;
142 /* static */
143 UPowerClient* UPowerClient::GetInstance() {
144 if (!sInstance) {
145 sInstance = new UPowerClient();
148 return sInstance;
151 UPowerClient::UPowerClient()
152 : mLevel(kDefaultLevel),
153 mCharging(kDefaultCharging),
154 mRemainingTime(kDefaultRemainingTime) {}
156 UPowerClient::~UPowerClient() {
157 NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice &&
158 !mTrackedDeviceProxy,
159 "The observers have not been correctly removed! "
160 "(StopListening should have been called)");
163 void UPowerClient::BeginListening() {
164 GUniquePtr<GError> error;
165 mDBusConnection =
166 dont_AddRef(dbus_g_bus_get(DBUS_BUS_SYSTEM, getter_Transfers(error)));
168 if (!mDBusConnection) {
169 HAL_LOG("Failed to open connection to bus: %s\n", error->message);
170 return;
173 DBusConnection* dbusConnection =
174 dbus_g_connection_get_connection(mDBusConnection);
176 // Make sure we do not exit the entire program if DBus connection get lost.
177 dbus_connection_set_exit_on_disconnect(dbusConnection, false);
179 // Listening to signals the DBus connection is going to get so we will know
180 // when it is lost and we will be able to disconnect cleanly.
181 dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
182 nullptr);
184 mUPowerProxy = dont_AddRef(dbus_g_proxy_new_for_name(
185 mDBusConnection, "org.freedesktop.UPower", "/org/freedesktop/UPower",
186 "org.freedesktop.UPower"));
188 UpdateTrackedDeviceSync();
191 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
192 * If we do that, we would have to disconnect from those in StopListening.
193 * It's not yet implemented because it requires testing hot plugging and
194 * removal of a battery.
196 dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING,
197 G_TYPE_INVALID);
198 dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged",
199 G_CALLBACK(DeviceChanged), this, nullptr);
202 void UPowerClient::StopListening() {
203 // If mDBusConnection isn't initialized, that means we are not really
204 // listening.
205 if (!mDBusConnection) {
206 return;
209 dbus_connection_remove_filter(
210 dbus_g_connection_get_connection(mDBusConnection), ConnectionSignalFilter,
211 this);
213 dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
214 G_CALLBACK(DeviceChanged), this);
216 mTrackedDevice = nullptr;
218 if (mTrackedDeviceProxy) {
219 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged",
220 G_CALLBACK(PropertiesChanged), this);
221 mTrackedDeviceProxy = nullptr;
224 mUPowerProxy = nullptr;
225 mDBusConnection = nullptr;
227 // We should now show the default values, not the latest we got.
228 mLevel = kDefaultLevel;
229 mCharging = kDefaultCharging;
230 mRemainingTime = kDefaultRemainingTime;
233 void UPowerClient::UpdateTrackedDeviceSync() {
234 GType typeGPtrArray =
235 dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH);
236 GPtrArray* devices = nullptr;
238 // Reset the current tracked device:
239 mTrackedDevice = nullptr;
241 // Reset the current tracked device proxy:
242 if (mTrackedDeviceProxy) {
243 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy, "PropertiesChanged",
244 G_CALLBACK(PropertiesChanged), this);
245 mTrackedDeviceProxy = nullptr;
248 GUniquePtr<GError> error;
249 // If that fails, that likely means upower isn't installed.
250 if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices",
251 getter_Transfers(error), G_TYPE_INVALID, typeGPtrArray,
252 &devices, G_TYPE_INVALID)) {
253 HAL_LOG("Error: %s\n", error->message);
254 return;
258 * We are looking for the first device that is a battery.
259 * TODO: we could try to combine more than one battery.
261 for (guint i = 0; i < devices->len; ++i) {
262 GUniquePtr<gchar> devicePath(
263 static_cast<gchar*>(g_ptr_array_index(devices, i)));
264 if (mTrackedDevice) {
265 continue;
268 RefPtr<DBusGProxy> proxy = dont_AddRef(dbus_g_proxy_new_from_proxy(
269 mUPowerProxy, "org.freedesktop.DBus.Properties", devicePath.get()));
271 RefPtr<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
273 if (g_value_get_uint(static_cast<const GValue*>(
274 g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) {
275 UpdateSavedInfo(hashTable);
276 mTrackedDevice = std::move(devicePath);
277 mTrackedDeviceProxy = std::move(proxy);
278 // Can't break here because we still need to iterate over all other
279 // devices to free them.
283 if (mTrackedDeviceProxy) {
284 dbus_g_proxy_add_signal(
285 mTrackedDeviceProxy, "PropertiesChanged", G_TYPE_STRING,
286 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
287 G_TYPE_STRV, G_TYPE_INVALID);
288 dbus_g_proxy_connect_signal(mTrackedDeviceProxy, "PropertiesChanged",
289 G_CALLBACK(PropertiesChanged), this, nullptr);
292 g_ptr_array_free(devices, true);
295 /* static */
296 void UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
297 UPowerClient* aListener) {
298 if (!aListener->mTrackedDevice) {
299 return;
302 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
303 if (g_strcmp0(aObjectPath, aListener->mTrackedDevice.get())) {
304 #else
305 if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice.get())) {
306 #endif
307 return;
310 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
313 /* static */
314 void UPowerClient::PropertiesChanged(DBusGProxy* aProxy, const gchar*,
315 GHashTable*, char**,
316 UPowerClient* aListener) {
317 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
320 /* static */
321 DBusHandlerResult UPowerClient::ConnectionSignalFilter(
322 DBusConnection* aConnection, DBusMessage* aMessage, void* aData) {
323 if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
324 static_cast<UPowerClient*>(aData)->StopListening();
325 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
326 // might be shared and some other filters might want to do something.
329 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
332 already_AddRefed<GHashTable> UPowerClient::GetDevicePropertiesSync(
333 DBusGProxy* aProxy) {
334 GUniquePtr<GError> error;
335 RefPtr<GHashTable> hashTable;
336 GType typeGHashTable =
337 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
338 if (!dbus_g_proxy_call(aProxy, "GetAll", getter_Transfers(error),
339 G_TYPE_STRING, "org.freedesktop.UPower.Device",
340 G_TYPE_INVALID, typeGHashTable,
341 hashTable.StartAssignment(), G_TYPE_INVALID)) {
342 HAL_LOG("Error: %s\n", error->message);
343 return nullptr;
346 return hashTable.forget();
349 /* static */
350 void UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy,
351 DBusGProxyCall* aCall,
352 void* aData) {
353 GUniquePtr<GError> error;
354 RefPtr<GHashTable> hashTable;
355 GType typeGHashTable =
356 dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
357 if (!dbus_g_proxy_end_call(aProxy, aCall, getter_Transfers(error),
358 typeGHashTable, hashTable.StartAssignment(),
359 G_TYPE_INVALID)) {
360 HAL_LOG("Error: %s\n", error->message);
361 } else {
362 sInstance->UpdateSavedInfo(hashTable);
363 hal::NotifyBatteryChange(hal::BatteryInformation(
364 sInstance->mLevel, sInstance->mCharging, sInstance->mRemainingTime));
365 g_hash_table_unref(hashTable);
369 void UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) {
370 dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback,
371 nullptr, nullptr, G_TYPE_STRING,
372 "org.freedesktop.UPower.Device", G_TYPE_INVALID);
375 void UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) {
376 bool isFull = false;
379 * State values are confusing...
380 * First of all, after looking at upower sources (0.9.13), it seems that
381 * PendingDischarge and PendingCharge are not used.
382 * In addition, FullyCharged and Empty states are not clear because we do not
383 * know if the battery is actually charging or not. Those values come directly
384 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
385 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
386 * related to the level, not to the charging state.
387 * In this code, we are going to assume that Full means charging and Empty
388 * means discharging because if that is not the case, the state should not
389 * last a long time (actually, it should disappear at the following update).
390 * It might be even very hard to see real cases where the state is Empty and
391 * the battery is charging or the state is Full and the battery is discharging
392 * given that plugging/unplugging the battery should have an impact on the
393 * level.
395 switch (g_value_get_uint(
396 static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
397 case eState_Unknown:
398 mCharging = kDefaultCharging;
399 break;
400 case eState_FullyCharged:
401 isFull = true;
402 [[fallthrough]];
403 case eState_Charging:
404 case eState_PendingCharge:
405 mCharging = true;
406 break;
407 case eState_Discharging:
408 case eState_Empty:
409 case eState_PendingDischarge:
410 mCharging = false;
411 break;
415 * The battery level might be very close to 100% (like 99%) without
416 * increasing. It seems that upower sets the battery state as 'full' in that
417 * case so we should trust it and not even try to get the value.
419 if (isFull) {
420 mLevel = 1.0;
421 } else {
422 mLevel = round(g_value_get_double(static_cast<const GValue*>(
423 g_hash_table_lookup(aHashTable, "Percentage")))) *
424 0.01;
427 if (isFull) {
428 mRemainingTime = 0;
429 } else {
430 mRemainingTime = mCharging
431 ? g_value_get_int64(static_cast<const GValue*>(
432 g_hash_table_lookup(aHashTable, "TimeToFull")))
433 : g_value_get_int64(static_cast<const GValue*>(
434 g_hash_table_lookup(aHashTable, "TimeToEmpty")));
436 if (mRemainingTime == kUPowerUnknownRemainingTime) {
437 mRemainingTime = kUnknownRemainingTime;
442 double UPowerClient::GetLevel() { return mLevel; }
444 bool UPowerClient::IsCharging() { return mCharging; }
446 double UPowerClient::GetRemainingTime() { return mRemainingTime; }
448 } // namespace mozilla::hal_impl