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/dom/battery/Constants.h>
11 #include "nsAutoRef.h"
15 * Helper that manages the destruction of glib objects as soon as they leave
18 * We are specializing nsAutoRef class.
22 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.
41 static UPowerClient
* GetInstance();
43 void BeginListening();
48 double GetRemainingTime();
62 eState_PendingDischarge
66 * Update the currently tracked device.
67 * @return whether everything went ok.
69 void UpdateTrackedDeviceSync();
72 * Returns a hash table with the properties of aDevice.
73 * Note: the caller has to unref the hash table.
75 GHashTable
* GetDevicePropertiesSync(DBusGProxy
* aProxy
);
76 void GetDevicePropertiesAsync(DBusGProxy
* aProxy
);
77 static void GetDevicePropertiesCallback(DBusGProxy
* aProxy
,
78 DBusGProxyCall
* aCall
,
82 * Using the device properties (aHashTable), this method updates the member
83 * variable storing the values we care about.
85 void UpdateSavedInfo(GHashTable
* aHashTable
);
88 * Callback used by 'DeviceChanged' signal.
90 static void DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
,
91 UPowerClient
* aListener
);
94 * Callback called when mDBusConnection gets a signal.
96 static DBusHandlerResult
ConnectionSignalFilter(DBusConnection
* aConnection
,
97 DBusMessage
* aMessage
,
100 // The DBus connection object.
101 DBusGConnection
* mDBusConnection
;
103 // The DBus proxy object to upower.
104 DBusGProxy
* mUPowerProxy
;
106 // The path of the tracked device.
107 gchar
* mTrackedDevice
;
109 // The DBusGProxy for the tracked device.
110 DBusGProxy
* mTrackedDeviceProxy
;
114 double mRemainingTime
;
116 static UPowerClient
* sInstance
;
118 static const guint sDeviceTypeBattery
= 2;
119 static const guint64 kUPowerUnknownRemainingTime
= 0;
123 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
124 * mozilla::hal_impl::DisableBatteryNotifications,
125 * and mozilla::hal_impl::GetCurrentBatteryInformation.
129 EnableBatteryNotifications()
131 UPowerClient::GetInstance()->BeginListening();
135 DisableBatteryNotifications()
137 UPowerClient::GetInstance()->StopListening();
141 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;
156 /* static */ UPowerClient
*
157 UPowerClient::GetInstance()
160 sInstance
= new UPowerClient();
166 UPowerClient::UPowerClient()
167 : mDBusConnection(nullptr)
168 , mUPowerProxy(nullptr)
169 , mTrackedDevice(nullptr)
170 , mTrackedDeviceProxy(nullptr)
171 , mLevel(kDefaultLevel
)
172 , mCharging(kDefaultCharging
)
173 , mRemainingTime(kDefaultRemainingTime
)
177 UPowerClient::~UPowerClient()
179 NS_ASSERTION(!mDBusConnection
&& !mUPowerProxy
&& !mTrackedDevice
&& !mTrackedDeviceProxy
,
180 "The observers have not been correctly removed! "
181 "(StopListening should have been called)");
185 UPowerClient::BeginListening()
187 GError
* error
= nullptr;
188 mDBusConnection
= dbus_g_bus_get(DBUS_BUS_SYSTEM
, &error
);
190 if (!mDBusConnection
) {
191 HAL_LOG("Failed to open connection to bus: %s\n", error
->message
);
196 DBusConnection
* dbusConnection
=
197 dbus_g_connection_get_connection(mDBusConnection
);
199 // Make sure we do not exit the entire program if DBus connection get lost.
200 dbus_connection_set_exit_on_disconnect(dbusConnection
, false);
202 // Listening to signals the DBus connection is going to get so we will know
203 // when it is lost and we will be able to disconnect cleanly.
204 dbus_connection_add_filter(dbusConnection
, ConnectionSignalFilter
, this,
207 mUPowerProxy
= dbus_g_proxy_new_for_name(mDBusConnection
,
208 "org.freedesktop.UPower",
209 "/org/freedesktop/UPower",
210 "org.freedesktop.UPower");
212 UpdateTrackedDeviceSync();
215 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
216 * If we do that, we would have to disconnect from those in StopListening.
217 * It's not yet implemented because it requires testing hot plugging and
218 * removal of a battery.
220 dbus_g_proxy_add_signal(mUPowerProxy
, "DeviceChanged", G_TYPE_STRING
,
222 dbus_g_proxy_connect_signal(mUPowerProxy
, "DeviceChanged",
223 G_CALLBACK (DeviceChanged
), this, nullptr);
227 UPowerClient::StopListening()
229 // If mDBusConnection isn't initialized, that means we are not really listening.
230 if (!mDBusConnection
) {
234 dbus_connection_remove_filter(
235 dbus_g_connection_get_connection(mDBusConnection
),
236 ConnectionSignalFilter
, this);
238 dbus_g_proxy_disconnect_signal(mUPowerProxy
, "DeviceChanged",
239 G_CALLBACK (DeviceChanged
), this);
241 g_free(mTrackedDevice
);
242 mTrackedDevice
= nullptr;
244 if (mTrackedDeviceProxy
) {
245 g_object_unref(mTrackedDeviceProxy
);
246 mTrackedDeviceProxy
= nullptr;
249 g_object_unref(mUPowerProxy
);
250 mUPowerProxy
= nullptr;
252 dbus_g_connection_unref(mDBusConnection
);
253 mDBusConnection
= nullptr;
255 // We should now show the default values, not the latest we got.
256 mLevel
= kDefaultLevel
;
257 mCharging
= kDefaultCharging
;
258 mRemainingTime
= kDefaultRemainingTime
;
262 UPowerClient::UpdateTrackedDeviceSync()
264 GType typeGPtrArray
= dbus_g_type_get_collection("GPtrArray",
265 DBUS_TYPE_G_OBJECT_PATH
);
266 GPtrArray
* devices
= nullptr;
267 GError
* error
= nullptr;
269 // Reset the current tracked device:
270 g_free(mTrackedDevice
);
271 mTrackedDevice
= nullptr;
273 // Reset the current tracked device proxy:
274 if (mTrackedDeviceProxy
) {
275 g_object_unref(mTrackedDeviceProxy
);
276 mTrackedDeviceProxy
= nullptr;
279 // If that fails, that likely means upower isn't installed.
280 if (!dbus_g_proxy_call(mUPowerProxy
, "EnumerateDevices", &error
, G_TYPE_INVALID
,
281 typeGPtrArray
, &devices
, G_TYPE_INVALID
)) {
282 HAL_LOG("Error: %s\n", error
->message
);
288 * We are looking for the first device that is a battery.
289 * TODO: we could try to combine more than one battery.
291 for (guint i
=0; i
<devices
->len
; ++i
) {
292 gchar
* devicePath
= static_cast<gchar
*>(g_ptr_array_index(devices
, i
));
294 DBusGProxy
* proxy
= dbus_g_proxy_new_from_proxy(mUPowerProxy
,
295 "org.freedesktop.DBus.Properties",
298 nsAutoRef
<GHashTable
> hashTable(GetDevicePropertiesSync(proxy
));
300 if (g_value_get_uint(static_cast<const GValue
*>(g_hash_table_lookup(hashTable
, "Type"))) == sDeviceTypeBattery
) {
301 UpdateSavedInfo(hashTable
);
302 mTrackedDevice
= devicePath
;
303 mTrackedDeviceProxy
= proxy
;
307 g_object_unref(proxy
);
311 g_ptr_array_free(devices
, true);
315 UPowerClient::DeviceChanged(DBusGProxy
* aProxy
, const gchar
* aObjectPath
, UPowerClient
* aListener
)
317 if (!aListener
->mTrackedDevice
) {
321 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
322 if (g_strcmp0(aObjectPath
, aListener
->mTrackedDevice
)) {
324 if (g_ascii_strcasecmp(aObjectPath
, aListener
->mTrackedDevice
)) {
329 aListener
->GetDevicePropertiesAsync(aListener
->mTrackedDeviceProxy
);
332 /* static */ DBusHandlerResult
333 UPowerClient::ConnectionSignalFilter(DBusConnection
* aConnection
,
334 DBusMessage
* aMessage
, void* aData
)
336 if (dbus_message_is_signal(aMessage
, DBUS_INTERFACE_LOCAL
, "Disconnected")) {
337 static_cast<UPowerClient
*>(aData
)->StopListening();
338 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
339 // might be shared and some other filters might want to do something.
342 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
346 UPowerClient::GetDevicePropertiesSync(DBusGProxy
* aProxy
)
348 GError
* error
= nullptr;
349 GHashTable
* hashTable
= nullptr;
350 GType typeGHashTable
= dbus_g_type_get_map("GHashTable", G_TYPE_STRING
,
352 if (!dbus_g_proxy_call(aProxy
, "GetAll", &error
, G_TYPE_STRING
,
353 "org.freedesktop.UPower.Device", G_TYPE_INVALID
,
354 typeGHashTable
, &hashTable
, G_TYPE_INVALID
)) {
355 HAL_LOG("Error: %s\n", error
->message
);
364 UPowerClient::GetDevicePropertiesCallback(DBusGProxy
* aProxy
,
365 DBusGProxyCall
* aCall
, void* aData
)
367 GError
* error
= nullptr;
368 GHashTable
* hashTable
= nullptr;
369 GType typeGHashTable
= dbus_g_type_get_map("GHashTable", G_TYPE_STRING
,
371 if (!dbus_g_proxy_end_call(aProxy
, aCall
, &error
, typeGHashTable
,
372 &hashTable
, G_TYPE_INVALID
)) {
373 HAL_LOG("Error: %s\n", error
->message
);
376 sInstance
->UpdateSavedInfo(hashTable
);
377 hal::NotifyBatteryChange(hal::BatteryInformation(sInstance
->mLevel
,
378 sInstance
->mCharging
,
379 sInstance
->mRemainingTime
));
380 g_hash_table_unref(hashTable
);
385 UPowerClient::GetDevicePropertiesAsync(DBusGProxy
* aProxy
)
387 dbus_g_proxy_begin_call(aProxy
, "GetAll", GetDevicePropertiesCallback
, nullptr,
388 nullptr, G_TYPE_STRING
,
389 "org.freedesktop.UPower.Device", G_TYPE_INVALID
);
393 UPowerClient::UpdateSavedInfo(GHashTable
* aHashTable
)
398 * State values are confusing...
399 * First of all, after looking at upower sources (0.9.13), it seems that
400 * PendingDischarge and PendingCharge are not used.
401 * In addition, FullyCharged and Empty states are not clear because we do not
402 * know if the battery is actually charging or not. Those values come directly
403 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full",
404 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only
405 * related to the level, not to the charging state.
406 * In this code, we are going to assume that Full means charging and Empty
407 * means discharging because if that is not the case, the state should not
408 * last a long time (actually, it should disappear at the following update).
409 * It might be even very hard to see real cases where the state is Empty and
410 * the battery is charging or the state is Full and the battery is discharging
411 * given that plugging/unplugging the battery should have an impact on the
414 switch (g_value_get_uint(static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "State")))) {
416 mCharging
= kDefaultCharging
;
418 case eState_FullyCharged
:
420 case eState_Charging
:
421 case eState_PendingCharge
:
424 case eState_Discharging
:
426 case eState_PendingDischarge
:
432 * The battery level might be very close to 100% (like 99%) without
433 * increasing. It seems that upower sets the battery state as 'full' in that
434 * case so we should trust it and not even try to get the value.
439 mLevel
= round(g_value_get_double(static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "Percentage"))))*0.01;
445 mRemainingTime
= mCharging
? g_value_get_int64(static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "TimeToFull")))
446 : g_value_get_int64(static_cast<const GValue
*>(g_hash_table_lookup(aHashTable
, "TimeToEmpty")));
448 if (mRemainingTime
== kUPowerUnknownRemainingTime
) {
449 mRemainingTime
= kUnknownRemainingTime
;
455 UPowerClient::GetLevel()
461 UPowerClient::IsCharging()
467 UPowerClient::GetRemainingTime()
469 return mRemainingTime
;
472 } // namespace hal_impl
473 } // namespace mozilla