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 <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"
16 using namespace mozilla::dom::battery
;
18 namespace mozilla::hal_impl
{
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.
27 static UPowerClient
* GetInstance();
29 void BeginListening();
34 double GetRemainingTime();
48 eState_PendingDischarge
52 * Update the currently tracked device.
53 * @return whether everything went ok.
55 void UpdateTrackedDeviceSync();
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
);
66 * Using the device properties (aHashTable), this method updates the member
67 * variable storing the values we care about.
69 void UpdateSavedInfo(GHashTable
* aHashTable
);
72 * Callback used by 'DeviceChanged' signal.
74 static void DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
,
75 UPowerClient
* aListener
);
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
);
86 * Callback called when mDBusConnection gets a signal.
88 static DBusHandlerResult
ConnectionSignalFilter(DBusConnection
* aConnection
,
89 DBusMessage
* aMessage
,
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
;
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;
143 UPowerClient
* UPowerClient::GetInstance() {
145 sInstance
= new UPowerClient();
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
;
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
);
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,
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
,
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
205 if (!mDBusConnection
) {
209 dbus_connection_remove_filter(
210 dbus_g_connection_get_connection(mDBusConnection
), ConnectionSignalFilter
,
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
);
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
) {
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);
296 void UPowerClient::DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
,
297 UPowerClient
* aListener
) {
298 if (!aListener
->mTrackedDevice
) {
302 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
303 if (g_strcmp0(aObjectPath
, aListener
->mTrackedDevice
.get())) {
305 if (g_ascii_strcasecmp(aObjectPath
, aListener
->mTrackedDevice
.get())) {
310 aListener
->GetDevicePropertiesAsync(aListener
->mTrackedDeviceProxy
);
314 void UPowerClient::PropertiesChanged(DBusGProxy
* aProxy
, const gchar
*,
316 UPowerClient
* aListener
) {
317 aListener
->GetDevicePropertiesAsync(aListener
->mTrackedDeviceProxy
);
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
);
346 return hashTable
.forget();
350 void UPowerClient::GetDevicePropertiesCallback(DBusGProxy
* aProxy
,
351 DBusGProxyCall
* aCall
,
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(),
360 HAL_LOG("Error: %s\n", error
->message
);
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
) {
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
395 switch (g_value_get_uint(
396 static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "State")))) {
398 mCharging
= kDefaultCharging
;
400 case eState_FullyCharged
:
403 case eState_Charging
:
404 case eState_PendingCharge
:
407 case eState_Discharging
:
409 case eState_PendingDischarge
:
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.
422 mLevel
= round(g_value_get_double(static_cast<const GValue
*>(
423 g_hash_table_lookup(aHashTable
, "Percentage")))) *
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