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 "nsAutoRef.h"
16 * Helper that manages the destruction of glib objects as soon as they leave
19 * We are specializing nsAutoRef class.
23 class nsAutoRefTraits
<GHashTable
> : public nsPointerRefTraits
<GHashTable
> {
25 static void Release(GHashTable
* ptr
) { g_hash_table_unref(ptr
); }
28 using namespace mozilla::dom::battery
;
34 * This is the declaration of UPowerClient class. This class is listening and
35 * communicating to upower daemon through DBus.
36 * There is no header file because this class shouldn't be public.
40 static UPowerClient
* GetInstance();
42 void BeginListening();
47 double GetRemainingTime();
61 eState_PendingDischarge
65 * Update the currently tracked device.
66 * @return whether everything went ok.
68 void UpdateTrackedDeviceSync();
71 * Returns a hash table with the properties of aDevice.
72 * Note: the caller has to unref the hash table.
74 GHashTable
* GetDevicePropertiesSync(DBusGProxy
* aProxy
);
75 void GetDevicePropertiesAsync(DBusGProxy
* aProxy
);
76 static void GetDevicePropertiesCallback(DBusGProxy
* aProxy
,
77 DBusGProxyCall
* aCall
, void* aData
);
80 * Using the device properties (aHashTable), this method updates the member
81 * variable storing the values we care about.
83 void UpdateSavedInfo(GHashTable
* aHashTable
);
86 * Callback used by 'DeviceChanged' signal.
88 static void DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
,
89 UPowerClient
* aListener
);
92 * Callback used by 'PropertiesChanged' signal.
93 * This method is called when the the battery level changes.
94 * (Only with upower >= 0.99)
96 static void PropertiesChanged(DBusGProxy
* aProxy
, const gchar
*, GHashTable
*,
97 char**, UPowerClient
* aListener
);
100 * Callback called when mDBusConnection gets a signal.
102 static DBusHandlerResult
ConnectionSignalFilter(DBusConnection
* aConnection
,
103 DBusMessage
* aMessage
,
106 // The DBus connection object.
107 DBusGConnection
* mDBusConnection
;
109 // The DBus proxy object to upower.
110 DBusGProxy
* mUPowerProxy
;
112 // The path of the tracked device.
113 gchar
* mTrackedDevice
;
115 // The DBusGProxy for the tracked device.
116 DBusGProxy
* mTrackedDeviceProxy
;
120 double mRemainingTime
;
122 static UPowerClient
* sInstance
;
124 static const guint sDeviceTypeBattery
= 2;
125 static const guint64 kUPowerUnknownRemainingTime
= 0;
129 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
130 * mozilla::hal_impl::DisableBatteryNotifications,
131 * and mozilla::hal_impl::GetCurrentBatteryInformation.
134 void EnableBatteryNotifications() {
135 UPowerClient::GetInstance()->BeginListening();
138 void DisableBatteryNotifications() {
139 UPowerClient::GetInstance()->StopListening();
142 void GetCurrentBatteryInformation(hal::BatteryInformation
* aBatteryInfo
) {
143 UPowerClient
* upowerClient
= UPowerClient::GetInstance();
145 aBatteryInfo
->level() = upowerClient
->GetLevel();
146 aBatteryInfo
->charging() = upowerClient
->IsCharging();
147 aBatteryInfo
->remainingTime() = upowerClient
->GetRemainingTime();
151 * Following is the implementation of UPowerClient.
154 UPowerClient
* UPowerClient::sInstance
= nullptr;
157 UPowerClient
* UPowerClient::GetInstance() {
159 sInstance
= new UPowerClient();
165 UPowerClient::UPowerClient()
166 : mDBusConnection(nullptr),
167 mUPowerProxy(nullptr),
168 mTrackedDevice(nullptr),
169 mTrackedDeviceProxy(nullptr),
170 mLevel(kDefaultLevel
),
171 mCharging(kDefaultCharging
),
172 mRemainingTime(kDefaultRemainingTime
) {}
174 UPowerClient::~UPowerClient() {
175 NS_ASSERTION(!mDBusConnection
&& !mUPowerProxy
&& !mTrackedDevice
&&
176 !mTrackedDeviceProxy
,
177 "The observers have not been correctly removed! "
178 "(StopListening should have been called)");
181 void UPowerClient::BeginListening() {
182 GError
* error
= nullptr;
183 mDBusConnection
= dbus_g_bus_get(DBUS_BUS_SYSTEM
, &error
);
185 if (!mDBusConnection
) {
186 HAL_LOG("Failed to open connection to bus: %s\n", error
->message
);
191 DBusConnection
* dbusConnection
=
192 dbus_g_connection_get_connection(mDBusConnection
);
194 // Make sure we do not exit the entire program if DBus connection get lost.
195 dbus_connection_set_exit_on_disconnect(dbusConnection
, false);
197 // Listening to signals the DBus connection is going to get so we will know
198 // when it is lost and we will be able to disconnect cleanly.
199 dbus_connection_add_filter(dbusConnection
, ConnectionSignalFilter
, this,
202 mUPowerProxy
= dbus_g_proxy_new_for_name(
203 mDBusConnection
, "org.freedesktop.UPower", "/org/freedesktop/UPower",
204 "org.freedesktop.UPower");
206 UpdateTrackedDeviceSync();
209 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
210 * If we do that, we would have to disconnect from those in StopListening.
211 * It's not yet implemented because it requires testing hot plugging and
212 * removal of a battery.
214 dbus_g_proxy_add_signal(mUPowerProxy
, "DeviceChanged", G_TYPE_STRING
,
216 dbus_g_proxy_connect_signal(mUPowerProxy
, "DeviceChanged",
217 G_CALLBACK(DeviceChanged
), this, nullptr);
220 void UPowerClient::StopListening() {
221 // If mDBusConnection isn't initialized, that means we are not really
223 if (!mDBusConnection
) {
227 dbus_connection_remove_filter(
228 dbus_g_connection_get_connection(mDBusConnection
), ConnectionSignalFilter
,
231 dbus_g_proxy_disconnect_signal(mUPowerProxy
, "DeviceChanged",
232 G_CALLBACK(DeviceChanged
), this);
234 g_free(mTrackedDevice
);
235 mTrackedDevice
= nullptr;
237 if (mTrackedDeviceProxy
) {
238 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy
, "PropertiesChanged",
239 G_CALLBACK(PropertiesChanged
), this);
241 g_object_unref(mTrackedDeviceProxy
);
242 mTrackedDeviceProxy
= nullptr;
245 g_object_unref(mUPowerProxy
);
246 mUPowerProxy
= nullptr;
248 dbus_g_connection_unref(mDBusConnection
);
249 mDBusConnection
= nullptr;
251 // We should now show the default values, not the latest we got.
252 mLevel
= kDefaultLevel
;
253 mCharging
= kDefaultCharging
;
254 mRemainingTime
= kDefaultRemainingTime
;
257 void UPowerClient::UpdateTrackedDeviceSync() {
258 GType typeGPtrArray
=
259 dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH
);
260 GPtrArray
* devices
= nullptr;
261 GError
* error
= nullptr;
263 // Reset the current tracked device:
264 g_free(mTrackedDevice
);
265 mTrackedDevice
= nullptr;
267 // Reset the current tracked device proxy:
268 if (mTrackedDeviceProxy
) {
269 dbus_g_proxy_disconnect_signal(mTrackedDeviceProxy
, "PropertiesChanged",
270 G_CALLBACK(PropertiesChanged
), this);
272 g_object_unref(mTrackedDeviceProxy
);
273 mTrackedDeviceProxy
= nullptr;
276 // If that fails, that likely means upower isn't installed.
277 if (!dbus_g_proxy_call(mUPowerProxy
, "EnumerateDevices", &error
,
278 G_TYPE_INVALID
, typeGPtrArray
, &devices
,
280 HAL_LOG("Error: %s\n", error
->message
);
286 * We are looking for the first device that is a battery.
287 * TODO: we could try to combine more than one battery.
289 for (guint i
= 0; i
< devices
->len
; ++i
) {
290 gchar
* devicePath
= static_cast<gchar
*>(g_ptr_array_index(devices
, i
));
292 DBusGProxy
* proxy
= dbus_g_proxy_new_from_proxy(
293 mUPowerProxy
, "org.freedesktop.DBus.Properties", devicePath
);
295 nsAutoRef
<GHashTable
> hashTable(GetDevicePropertiesSync(proxy
));
297 if (g_value_get_uint(static_cast<const GValue
*>(
298 g_hash_table_lookup(hashTable
, "Type"))) == sDeviceTypeBattery
) {
299 UpdateSavedInfo(hashTable
);
300 mTrackedDevice
= devicePath
;
301 mTrackedDeviceProxy
= proxy
;
305 g_object_unref(proxy
);
309 if (mTrackedDeviceProxy
) {
310 dbus_g_proxy_add_signal(
311 mTrackedDeviceProxy
, "PropertiesChanged", G_TYPE_STRING
,
312 dbus_g_type_get_map("GHashTable", G_TYPE_STRING
, G_TYPE_VALUE
),
313 G_TYPE_STRV
, G_TYPE_INVALID
);
314 dbus_g_proxy_connect_signal(mTrackedDeviceProxy
, "PropertiesChanged",
315 G_CALLBACK(PropertiesChanged
), this, nullptr);
318 g_ptr_array_free(devices
, true);
322 void UPowerClient::DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
,
323 UPowerClient
* aListener
) {
324 if (!aListener
->mTrackedDevice
) {
328 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
329 if (g_strcmp0(aObjectPath
, aListener
->mTrackedDevice
)) {
331 if (g_ascii_strcasecmp(aObjectPath
, aListener
->mTrackedDevice
)) {
336 aListener
->GetDevicePropertiesAsync(aListener
->mTrackedDeviceProxy
);
340 void UPowerClient::PropertiesChanged(DBusGProxy
* aProxy
, const gchar
*,
342 UPowerClient
* aListener
) {
343 aListener
->GetDevicePropertiesAsync(aListener
->mTrackedDeviceProxy
);
347 DBusHandlerResult
UPowerClient::ConnectionSignalFilter(
348 DBusConnection
* aConnection
, DBusMessage
* aMessage
, void* aData
) {
349 if (dbus_message_is_signal(aMessage
, DBUS_INTERFACE_LOCAL
, "Disconnected")) {
350 static_cast<UPowerClient
*>(aData
)->StopListening();
351 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
352 // might be shared and some other filters might want to do something.
355 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
358 GHashTable
* UPowerClient::GetDevicePropertiesSync(DBusGProxy
* aProxy
) {
359 GError
* error
= nullptr;
360 GHashTable
* hashTable
= nullptr;
361 GType typeGHashTable
=
362 dbus_g_type_get_map("GHashTable", G_TYPE_STRING
, G_TYPE_VALUE
);
363 if (!dbus_g_proxy_call(aProxy
, "GetAll", &error
, G_TYPE_STRING
,
364 "org.freedesktop.UPower.Device", G_TYPE_INVALID
,
365 typeGHashTable
, &hashTable
, G_TYPE_INVALID
)) {
366 HAL_LOG("Error: %s\n", error
->message
);
375 void UPowerClient::GetDevicePropertiesCallback(DBusGProxy
* aProxy
,
376 DBusGProxyCall
* aCall
,
378 GError
* error
= nullptr;
379 GHashTable
* hashTable
= nullptr;
380 GType typeGHashTable
=
381 dbus_g_type_get_map("GHashTable", G_TYPE_STRING
, G_TYPE_VALUE
);
382 if (!dbus_g_proxy_end_call(aProxy
, aCall
, &error
, typeGHashTable
, &hashTable
,
384 HAL_LOG("Error: %s\n", error
->message
);
387 sInstance
->UpdateSavedInfo(hashTable
);
388 hal::NotifyBatteryChange(hal::BatteryInformation(
389 sInstance
->mLevel
, sInstance
->mCharging
, sInstance
->mRemainingTime
));
390 g_hash_table_unref(hashTable
);
394 void UPowerClient::GetDevicePropertiesAsync(DBusGProxy
* aProxy
) {
395 dbus_g_proxy_begin_call(aProxy
, "GetAll", GetDevicePropertiesCallback
,
396 nullptr, nullptr, G_TYPE_STRING
,
397 "org.freedesktop.UPower.Device", G_TYPE_INVALID
);
400 void UPowerClient::UpdateSavedInfo(GHashTable
* aHashTable
) {
404 * State values are confusing...
405 * First of all, after looking at upower sources (0.9.13), it seems that
406 * PendingDischarge and PendingCharge are not used.
407 * In addition, FullyCharged and Empty states are not clear because we do not
408 * know if the battery is actually charging or not. Those values come directly
409 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
410 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
411 * related to the level, not to the charging state.
412 * In this code, we are going to assume that Full means charging and Empty
413 * means discharging because if that is not the case, the state should not
414 * last a long time (actually, it should disappear at the following update).
415 * It might be even very hard to see real cases where the state is Empty and
416 * the battery is charging or the state is Full and the battery is discharging
417 * given that plugging/unplugging the battery should have an impact on the
420 switch (g_value_get_uint(
421 static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "State")))) {
423 mCharging
= kDefaultCharging
;
425 case eState_FullyCharged
:
428 case eState_Charging
:
429 case eState_PendingCharge
:
432 case eState_Discharging
:
434 case eState_PendingDischarge
:
440 * The battery level might be very close to 100% (like 99%) without
441 * increasing. It seems that upower sets the battery state as 'full' in that
442 * case so we should trust it and not even try to get the value.
447 mLevel
= round(g_value_get_double(static_cast<const GValue
*>(
448 g_hash_table_lookup(aHashTable
, "Percentage")))) *
455 mRemainingTime
= mCharging
456 ? g_value_get_int64(static_cast<const GValue
*>(
457 g_hash_table_lookup(aHashTable
, "TimeToFull")))
458 : g_value_get_int64(static_cast<const GValue
*>(
459 g_hash_table_lookup(aHashTable
, "TimeToEmpty")));
461 if (mRemainingTime
== kUPowerUnknownRemainingTime
) {
462 mRemainingTime
= kUnknownRemainingTime
;
467 double UPowerClient::GetLevel() { return mLevel
; }
469 bool UPowerClient::IsCharging() { return mCharging
; }
471 double UPowerClient::GetRemainingTime() { return mRemainingTime
; }
473 } // namespace hal_impl
474 } // namespace mozilla