hal/linux/UPowerClient.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial