|
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/. */ |
|
5 |
|
6 #include <mozilla/Hal.h> |
|
7 #include <dbus/dbus-glib.h> |
|
8 #include <dbus/dbus-glib-lowlevel.h> |
|
9 #include <mozilla/dom/battery/Constants.h> |
|
10 #include "nsAutoRef.h" |
|
11 |
|
12 /* |
|
13 * Helper that manages the destruction of glib objects as soon as they leave |
|
14 * the current scope. |
|
15 * |
|
16 * We are specializing nsAutoRef class. |
|
17 */ |
|
18 |
|
19 template <> |
|
20 class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable> |
|
21 { |
|
22 public: |
|
23 static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); } |
|
24 }; |
|
25 |
|
26 using namespace mozilla::dom::battery; |
|
27 |
|
28 namespace mozilla { |
|
29 namespace hal_impl { |
|
30 |
|
31 /** |
|
32 * This is the declaration of UPowerClient class. This class is listening and |
|
33 * communicating to upower daemon through DBus. |
|
34 * There is no header file because this class shouldn't be public. |
|
35 */ |
|
36 class UPowerClient |
|
37 { |
|
38 public: |
|
39 static UPowerClient* GetInstance(); |
|
40 |
|
41 void BeginListening(); |
|
42 void StopListening(); |
|
43 |
|
44 double GetLevel(); |
|
45 bool IsCharging(); |
|
46 double GetRemainingTime(); |
|
47 |
|
48 ~UPowerClient(); |
|
49 |
|
50 private: |
|
51 UPowerClient(); |
|
52 |
|
53 enum States { |
|
54 eState_Unknown = 0, |
|
55 eState_Charging, |
|
56 eState_Discharging, |
|
57 eState_Empty, |
|
58 eState_FullyCharged, |
|
59 eState_PendingCharge, |
|
60 eState_PendingDischarge |
|
61 }; |
|
62 |
|
63 /** |
|
64 * Update the currently tracked device. |
|
65 * @return whether everything went ok. |
|
66 */ |
|
67 void UpdateTrackedDeviceSync(); |
|
68 |
|
69 /** |
|
70 * Returns a hash table with the properties of aDevice. |
|
71 * Note: the caller has to unref the hash table. |
|
72 */ |
|
73 GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy); |
|
74 void GetDevicePropertiesAsync(DBusGProxy* aProxy); |
|
75 static void GetDevicePropertiesCallback(DBusGProxy* aProxy, |
|
76 DBusGProxyCall* aCall, |
|
77 void* aData); |
|
78 |
|
79 /** |
|
80 * Using the device properties (aHashTable), this method updates the member |
|
81 * variable storing the values we care about. |
|
82 */ |
|
83 void UpdateSavedInfo(GHashTable* aHashTable); |
|
84 |
|
85 /** |
|
86 * Callback used by 'DeviceChanged' signal. |
|
87 */ |
|
88 static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, |
|
89 UPowerClient* aListener); |
|
90 |
|
91 /** |
|
92 * Callback called when mDBusConnection gets a signal. |
|
93 */ |
|
94 static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection, |
|
95 DBusMessage* aMessage, |
|
96 void* aData); |
|
97 |
|
98 // The DBus connection object. |
|
99 DBusGConnection* mDBusConnection; |
|
100 |
|
101 // The DBus proxy object to upower. |
|
102 DBusGProxy* mUPowerProxy; |
|
103 |
|
104 // The path of the tracked device. |
|
105 gchar* mTrackedDevice; |
|
106 |
|
107 // The DBusGProxy for the tracked device. |
|
108 DBusGProxy* mTrackedDeviceProxy; |
|
109 |
|
110 double mLevel; |
|
111 bool mCharging; |
|
112 double mRemainingTime; |
|
113 |
|
114 static UPowerClient* sInstance; |
|
115 |
|
116 static const guint sDeviceTypeBattery = 2; |
|
117 static const guint64 kUPowerUnknownRemainingTime = 0; |
|
118 }; |
|
119 |
|
120 /* |
|
121 * Implementation of mozilla::hal_impl::EnableBatteryNotifications, |
|
122 * mozilla::hal_impl::DisableBatteryNotifications, |
|
123 * and mozilla::hal_impl::GetCurrentBatteryInformation. |
|
124 */ |
|
125 |
|
126 void |
|
127 EnableBatteryNotifications() |
|
128 { |
|
129 UPowerClient::GetInstance()->BeginListening(); |
|
130 } |
|
131 |
|
132 void |
|
133 DisableBatteryNotifications() |
|
134 { |
|
135 UPowerClient::GetInstance()->StopListening(); |
|
136 } |
|
137 |
|
138 void |
|
139 GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) |
|
140 { |
|
141 UPowerClient* upowerClient = UPowerClient::GetInstance(); |
|
142 |
|
143 aBatteryInfo->level() = upowerClient->GetLevel(); |
|
144 aBatteryInfo->charging() = upowerClient->IsCharging(); |
|
145 aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime(); |
|
146 } |
|
147 |
|
148 /* |
|
149 * Following is the implementation of UPowerClient. |
|
150 */ |
|
151 |
|
152 UPowerClient* UPowerClient::sInstance = nullptr; |
|
153 |
|
154 /* static */ UPowerClient* |
|
155 UPowerClient::GetInstance() |
|
156 { |
|
157 if (!sInstance) { |
|
158 sInstance = new UPowerClient(); |
|
159 } |
|
160 |
|
161 return sInstance; |
|
162 } |
|
163 |
|
164 UPowerClient::UPowerClient() |
|
165 : mDBusConnection(nullptr) |
|
166 , mUPowerProxy(nullptr) |
|
167 , mTrackedDevice(nullptr) |
|
168 , mTrackedDeviceProxy(nullptr) |
|
169 , mLevel(kDefaultLevel) |
|
170 , mCharging(kDefaultCharging) |
|
171 , mRemainingTime(kDefaultRemainingTime) |
|
172 { |
|
173 } |
|
174 |
|
175 UPowerClient::~UPowerClient() |
|
176 { |
|
177 NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy, |
|
178 "The observers have not been correctly removed! " |
|
179 "(StopListening should have been called)"); |
|
180 } |
|
181 |
|
182 void |
|
183 UPowerClient::BeginListening() |
|
184 { |
|
185 GError* error = nullptr; |
|
186 mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); |
|
187 |
|
188 if (!mDBusConnection) { |
|
189 g_printerr("Failed to open connection to bus: %s\n", error->message); |
|
190 g_error_free(error); |
|
191 return; |
|
192 } |
|
193 |
|
194 DBusConnection* dbusConnection = |
|
195 dbus_g_connection_get_connection(mDBusConnection); |
|
196 |
|
197 // Make sure we do not exit the entire program if DBus connection get lost. |
|
198 dbus_connection_set_exit_on_disconnect(dbusConnection, false); |
|
199 |
|
200 // Listening to signals the DBus connection is going to get so we will know |
|
201 // when it is lost and we will be able to disconnect cleanly. |
|
202 dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this, |
|
203 nullptr); |
|
204 |
|
205 mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection, |
|
206 "org.freedesktop.UPower", |
|
207 "/org/freedesktop/UPower", |
|
208 "org.freedesktop.UPower"); |
|
209 |
|
210 UpdateTrackedDeviceSync(); |
|
211 |
|
212 /* |
|
213 * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals. |
|
214 * If we do that, we would have to disconnect from those in StopListening. |
|
215 * It's not yet implemented because it requires testing hot plugging and |
|
216 * removal of a battery. |
|
217 */ |
|
218 dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING, |
|
219 G_TYPE_INVALID); |
|
220 dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged", |
|
221 G_CALLBACK (DeviceChanged), this, nullptr); |
|
222 } |
|
223 |
|
224 void |
|
225 UPowerClient::StopListening() |
|
226 { |
|
227 // If mDBusConnection isn't initialized, that means we are not really listening. |
|
228 if (!mDBusConnection) { |
|
229 return; |
|
230 } |
|
231 |
|
232 dbus_connection_remove_filter( |
|
233 dbus_g_connection_get_connection(mDBusConnection), |
|
234 ConnectionSignalFilter, this); |
|
235 |
|
236 dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged", |
|
237 G_CALLBACK (DeviceChanged), this); |
|
238 |
|
239 g_free(mTrackedDevice); |
|
240 mTrackedDevice = nullptr; |
|
241 |
|
242 if (mTrackedDeviceProxy) { |
|
243 g_object_unref(mTrackedDeviceProxy); |
|
244 mTrackedDeviceProxy = nullptr; |
|
245 } |
|
246 |
|
247 g_object_unref(mUPowerProxy); |
|
248 mUPowerProxy = nullptr; |
|
249 |
|
250 dbus_g_connection_unref(mDBusConnection); |
|
251 mDBusConnection = nullptr; |
|
252 |
|
253 // We should now show the default values, not the latest we got. |
|
254 mLevel = kDefaultLevel; |
|
255 mCharging = kDefaultCharging; |
|
256 mRemainingTime = kDefaultRemainingTime; |
|
257 } |
|
258 |
|
259 void |
|
260 UPowerClient::UpdateTrackedDeviceSync() |
|
261 { |
|
262 GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray", |
|
263 DBUS_TYPE_G_OBJECT_PATH); |
|
264 GPtrArray* devices = nullptr; |
|
265 GError* error = nullptr; |
|
266 |
|
267 // Reset the current tracked device: |
|
268 g_free(mTrackedDevice); |
|
269 mTrackedDevice = nullptr; |
|
270 |
|
271 // Reset the current tracked device proxy: |
|
272 if (mTrackedDeviceProxy) { |
|
273 g_object_unref(mTrackedDeviceProxy); |
|
274 mTrackedDeviceProxy = nullptr; |
|
275 } |
|
276 |
|
277 // If that fails, that likely means upower isn't installed. |
|
278 if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID, |
|
279 typeGPtrArray, &devices, G_TYPE_INVALID)) { |
|
280 g_printerr ("Error: %s\n", error->message); |
|
281 g_error_free(error); |
|
282 return; |
|
283 } |
|
284 |
|
285 /* |
|
286 * We are looking for the first device that is a battery. |
|
287 * TODO: we could try to combine more than one battery. |
|
288 */ |
|
289 for (guint i=0; i<devices->len; ++i) { |
|
290 gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i)); |
|
291 |
|
292 DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy, |
|
293 "org.freedesktop.DBus.Properties", |
|
294 devicePath); |
|
295 |
|
296 nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy)); |
|
297 |
|
298 if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) { |
|
299 UpdateSavedInfo(hashTable); |
|
300 mTrackedDevice = devicePath; |
|
301 mTrackedDeviceProxy = proxy; |
|
302 break; |
|
303 } |
|
304 |
|
305 g_object_unref(proxy); |
|
306 g_free(devicePath); |
|
307 } |
|
308 |
|
309 g_ptr_array_free(devices, true); |
|
310 } |
|
311 |
|
312 /* static */ void |
|
313 UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener) |
|
314 { |
|
315 if (!aListener->mTrackedDevice) { |
|
316 return; |
|
317 } |
|
318 |
|
319 #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16 |
|
320 if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) { |
|
321 #else |
|
322 if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) { |
|
323 #endif |
|
324 return; |
|
325 } |
|
326 |
|
327 aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy); |
|
328 } |
|
329 |
|
330 /* static */ DBusHandlerResult |
|
331 UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection, |
|
332 DBusMessage* aMessage, void* aData) |
|
333 { |
|
334 if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) { |
|
335 static_cast<UPowerClient*>(aData)->StopListening(); |
|
336 // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection |
|
337 // might be shared and some other filters might want to do something. |
|
338 } |
|
339 |
|
340 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
341 } |
|
342 |
|
343 GHashTable* |
|
344 UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy) |
|
345 { |
|
346 GError* error = nullptr; |
|
347 GHashTable* hashTable = nullptr; |
|
348 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, |
|
349 G_TYPE_VALUE); |
|
350 if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING, |
|
351 "org.freedesktop.UPower.Device", G_TYPE_INVALID, |
|
352 typeGHashTable, &hashTable, G_TYPE_INVALID)) { |
|
353 g_printerr("Error: %s\n", error->message); |
|
354 g_error_free(error); |
|
355 return nullptr; |
|
356 } |
|
357 |
|
358 return hashTable; |
|
359 } |
|
360 |
|
361 /* static */ void |
|
362 UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy, |
|
363 DBusGProxyCall* aCall, void* aData) |
|
364 { |
|
365 GError* error = nullptr; |
|
366 GHashTable* hashTable = nullptr; |
|
367 GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, |
|
368 G_TYPE_VALUE); |
|
369 if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable, |
|
370 &hashTable, G_TYPE_INVALID)) { |
|
371 g_printerr("Error: %s\n", error->message); |
|
372 g_error_free(error); |
|
373 } else { |
|
374 sInstance->UpdateSavedInfo(hashTable); |
|
375 hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel, |
|
376 sInstance->mCharging, |
|
377 sInstance->mRemainingTime)); |
|
378 g_hash_table_unref(hashTable); |
|
379 } |
|
380 } |
|
381 |
|
382 void |
|
383 UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) |
|
384 { |
|
385 dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr, |
|
386 nullptr, G_TYPE_STRING, |
|
387 "org.freedesktop.UPower.Device", G_TYPE_INVALID); |
|
388 } |
|
389 |
|
390 void |
|
391 UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) |
|
392 { |
|
393 bool isFull = false; |
|
394 |
|
395 /* |
|
396 * State values are confusing... |
|
397 * First of all, after looking at upower sources (0.9.13), it seems that |
|
398 * PendingDischarge and PendingCharge are not used. |
|
399 * In addition, FullyCharged and Empty states are not clear because we do not |
|
400 * know if the battery is actually charging or not. Those values come directly |
|
401 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full", |
|
402 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only |
|
403 * related to the level, not to the charging state. |
|
404 * In this code, we are going to assume that Full means charging and Empty |
|
405 * means discharging because if that is not the case, the state should not |
|
406 * last a long time (actually, it should disappear at the following update). |
|
407 * It might be even very hard to see real cases where the state is Empty and |
|
408 * the battery is charging or the state is Full and the battery is discharging |
|
409 * given that plugging/unplugging the battery should have an impact on the |
|
410 * level. |
|
411 */ |
|
412 switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) { |
|
413 case eState_Unknown: |
|
414 mCharging = kDefaultCharging; |
|
415 break; |
|
416 case eState_FullyCharged: |
|
417 isFull = true; |
|
418 case eState_Charging: |
|
419 case eState_PendingCharge: |
|
420 mCharging = true; |
|
421 break; |
|
422 case eState_Discharging: |
|
423 case eState_Empty: |
|
424 case eState_PendingDischarge: |
|
425 mCharging = false; |
|
426 break; |
|
427 } |
|
428 |
|
429 /* |
|
430 * The battery level might be very close to 100% (like 99.xxxx%) without |
|
431 * increasing. It seems that upower sets the battery state as 'full' in that |
|
432 * case so we should trust it and not even try to get the value. |
|
433 */ |
|
434 if (isFull) { |
|
435 mLevel = 1.0; |
|
436 } else { |
|
437 mLevel = g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage")))*0.01; |
|
438 } |
|
439 |
|
440 if (isFull) { |
|
441 mRemainingTime = 0; |
|
442 } else { |
|
443 mRemainingTime = mCharging ? g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToFull"))) |
|
444 : g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToEmpty"))); |
|
445 |
|
446 if (mRemainingTime == kUPowerUnknownRemainingTime) { |
|
447 mRemainingTime = kUnknownRemainingTime; |
|
448 } |
|
449 } |
|
450 } |
|
451 |
|
452 double |
|
453 UPowerClient::GetLevel() |
|
454 { |
|
455 return mLevel; |
|
456 } |
|
457 |
|
458 bool |
|
459 UPowerClient::IsCharging() |
|
460 { |
|
461 return mCharging; |
|
462 } |
|
463 |
|
464 double |
|
465 UPowerClient::GetRemainingTime() |
|
466 { |
|
467 return mRemainingTime; |
|
468 } |
|
469 |
|
470 } // namespace hal_impl |
|
471 } // namespace mozilla |