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