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

     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/. */
     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"
    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  */
    19 template <>
    20 class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable>
    21 {
    22 public:
    23   static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); }
    24 };
    26 using namespace mozilla::dom::battery;
    28 namespace mozilla {
    29 namespace hal_impl {
    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();
    41   void BeginListening();
    42   void StopListening();
    44   double GetLevel();
    45   bool   IsCharging();
    46   double GetRemainingTime();
    48   ~UPowerClient();
    50 private:
    51   UPowerClient();
    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   };
    63   /**
    64    * Update the currently tracked device.
    65    * @return whether everything went ok.
    66    */
    67   void UpdateTrackedDeviceSync();
    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);
    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);
    85   /**
    86    * Callback used by 'DeviceChanged' signal.
    87    */
    88   static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
    89                             UPowerClient* aListener);
    91   /**
    92    * Callback called when mDBusConnection gets a signal.
    93    */
    94   static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
    95                                                   DBusMessage* aMessage,
    96                                                   void* aData);
    98   // The DBus connection object.
    99   DBusGConnection* mDBusConnection;
   101   // The DBus proxy object to upower.
   102   DBusGProxy* mUPowerProxy;
   104   // The path of the tracked device.
   105   gchar* mTrackedDevice;
   107   // The DBusGProxy for the tracked device.
   108   DBusGProxy* mTrackedDeviceProxy;
   110   double mLevel;
   111   bool mCharging;
   112   double mRemainingTime;
   114   static UPowerClient* sInstance;
   116   static const guint sDeviceTypeBattery = 2;
   117   static const guint64 kUPowerUnknownRemainingTime = 0;
   118 };
   120 /*
   121  * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
   122  *                   mozilla::hal_impl::DisableBatteryNotifications,
   123  *               and mozilla::hal_impl::GetCurrentBatteryInformation.
   124  */
   126 void
   127 EnableBatteryNotifications()
   128 {
   129   UPowerClient::GetInstance()->BeginListening();
   130 }
   132 void
   133 DisableBatteryNotifications()
   134 {
   135   UPowerClient::GetInstance()->StopListening();
   136 }
   138 void
   139 GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
   140 {
   141   UPowerClient* upowerClient = UPowerClient::GetInstance();
   143   aBatteryInfo->level() = upowerClient->GetLevel();
   144   aBatteryInfo->charging() = upowerClient->IsCharging();
   145   aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime();
   146 }
   148 /*
   149  * Following is the implementation of UPowerClient.
   150  */
   152 UPowerClient* UPowerClient::sInstance = nullptr;
   154 /* static */ UPowerClient*
   155 UPowerClient::GetInstance()
   156 {
   157   if (!sInstance) {
   158     sInstance = new UPowerClient();
   159   }
   161   return sInstance;
   162 }
   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 }
   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 }
   182 void
   183 UPowerClient::BeginListening()
   184 {
   185   GError* error = nullptr;
   186   mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
   188   if (!mDBusConnection) {
   189     g_printerr("Failed to open connection to bus: %s\n", error->message);
   190     g_error_free(error);
   191     return;
   192   }
   194   DBusConnection* dbusConnection =
   195     dbus_g_connection_get_connection(mDBusConnection);
   197   // Make sure we do not exit the entire program if DBus connection get lost.
   198   dbus_connection_set_exit_on_disconnect(dbusConnection, false);
   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);
   205   mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection,
   206                                            "org.freedesktop.UPower",
   207                                            "/org/freedesktop/UPower",
   208                                            "org.freedesktop.UPower");
   210   UpdateTrackedDeviceSync();
   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 }
   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   }
   232   dbus_connection_remove_filter(
   233       dbus_g_connection_get_connection(mDBusConnection),
   234       ConnectionSignalFilter, this);
   236   dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
   237                                  G_CALLBACK (DeviceChanged), this);
   239   g_free(mTrackedDevice);
   240   mTrackedDevice = nullptr;
   242   if (mTrackedDeviceProxy) {
   243     g_object_unref(mTrackedDeviceProxy);
   244     mTrackedDeviceProxy = nullptr;
   245   }
   247   g_object_unref(mUPowerProxy);
   248   mUPowerProxy = nullptr;
   250   dbus_g_connection_unref(mDBusConnection);
   251   mDBusConnection = nullptr;
   253   // We should now show the default values, not the latest we got.
   254   mLevel = kDefaultLevel;
   255   mCharging = kDefaultCharging;
   256   mRemainingTime = kDefaultRemainingTime;
   257 }
   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;
   267   // Reset the current tracked device:
   268   g_free(mTrackedDevice);
   269   mTrackedDevice = nullptr;
   271   // Reset the current tracked device proxy:
   272   if (mTrackedDeviceProxy) {
   273     g_object_unref(mTrackedDeviceProxy);
   274     mTrackedDeviceProxy = nullptr;
   275   }
   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   }
   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));
   292     DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy,
   293                                                     "org.freedesktop.DBus.Properties",
   294                                                     devicePath);
   296     nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy));
   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     }
   305     g_object_unref(proxy);
   306     g_free(devicePath);
   307   }
   309   g_ptr_array_free(devices, true);
   310 }
   312 /* static */ void
   313 UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener)
   314 {
   315   if (!aListener->mTrackedDevice) {
   316     return;
   317   }
   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   }
   327   aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy);
   328 }
   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   }
   340   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   341 }
   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   }
   358   return hashTable;
   359 }
   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 }
   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 }
   390 void
   391 UPowerClient::UpdateSavedInfo(GHashTable* aHashTable)
   392 {
   393   bool isFull = false;
   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   }
   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   }
   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")));
   446     if (mRemainingTime == kUPowerUnknownRemainingTime) {
   447       mRemainingTime = kUnknownRemainingTime;
   448     }
   449   }
   450 }
   452 double
   453 UPowerClient::GetLevel()
   454 {
   455   return mLevel;
   456 }
   458 bool
   459 UPowerClient::IsCharging()
   460 {
   461   return mCharging;
   462 }
   464 double
   465 UPowerClient::GetRemainingTime()
   466 {
   467   return mRemainingTime;
   468 }
   470 } // namespace hal_impl
   471 } // namespace mozilla

mercurial