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