Bug 696041 - Battery API backend for linux with upower. r=karlt,cjones sr=roc
authorMounir Lamouri <mounir.lamouri@gmail.com>
Thu, 03 Nov 2011 11:33:31 +0100
changeset 79679 72357a8f0d8979f4d47814e362f586308a5ffccb
parent 79678 044bfd4399347c9357c98278c41858c98042789b
child 79680 257a4dbd5b1e81468cc209f99ff12737fee2bae4
push idunknown
push userunknown
push dateunknown
reviewerskarlt, cjones, roc
bugs696041
milestone10.0a1
Bug 696041 - Battery API backend for linux with upower. r=karlt,cjones sr=roc
hal/Makefile.in
hal/linux/LinuxHal.cpp
hal/linux/UPowerClient.cpp
toolkit/library/Makefile.in
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -38,16 +38,17 @@
 DEPTH       = ..
 topsrcdir   = @top_srcdir@
 srcdir      = @srcdir@
 VPATH       = \
   $(srcdir) \
   $(srcdir)/android \
   $(srcdir)/fallback \
   $(srcdir)/sandbox \
+  $(srcdir)/linux \
   $(NULL)
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE         = hal
 LIBRARY_NAME   = hal_s
 FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY = 1
@@ -62,15 +63,23 @@ EXPORTS_mozilla = \
 
 CPPSRCS = \
   Hal.cpp \
   SandboxHal.cpp \
   $(NULL)
 
 ifeq (Android,$(OS_TARGET))
 CPPSRCS += AndroidHal.cpp
+else ifeq (Linux,$(OS_TARGET))
+CPPSRCS += LinuxHal.cpp
+ifdef MOZ_ENABLE_DBUS
+CPPSRCS += UPowerClient.cpp
+endif
 else
 CPPSRCS += FallbackHal.cpp
 endif
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
+
+CFLAGS          += $(MOZ_DBUS_GLIB_CFLAGS)
+CXXFLAGS        += $(MOZ_DBUS_GLIB_CFLAGS)
new file mode 100644
--- /dev/null
+++ b/hal/linux/LinuxHal.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "Hal.h"
+
+#ifndef MOZ_ENABLE_DBUS
+#include <mozilla/dom/battery/Constants.h>
+#endif // !MOZ_ENABLE_DBUS
+
+namespace mozilla {
+namespace hal_impl {
+
+void
+Vibrate(const nsTArray<uint32>& pattern)
+{}
+
+#ifndef MOZ_ENABLE_DBUS
+void
+EnableBatteryNotifications()
+{}
+
+void
+DisableBatteryNotifications()
+{}
+
+void
+GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
+{
+  aBatteryInfo->level() = dom::battery::kDefaultLevel;
+  aBatteryInfo->charging() = dom::battery::kDefaultCharging;
+}
+#endif // !MOZ_ENABLE_DBUS
+
+} // hal_impl
+} // mozilla
+
new file mode 100644
--- /dev/null
+++ b/hal/linux/UPowerClient.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mounir Lamouri <mounir.lamouri@mozilla.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <mozilla/Hal.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <mozilla/dom/battery/Constants.h>
+#include "nsAutoRef.h"
+
+/*
+ * Helper that manages the destruction of glib objects as soon as they leave
+ * the current scope.
+ *
+ * We are specializing nsAutoRef class.
+ */
+
+NS_SPECIALIZE_TEMPLATE
+class nsAutoRefTraits<DBusGProxy> : public nsPointerRefTraits<DBusGProxy>
+{
+public:
+  static void Release(DBusGProxy* ptr) { g_object_unref(ptr); }
+};
+
+NS_SPECIALIZE_TEMPLATE
+class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable>
+{
+public:
+  static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); }
+};
+
+using namespace mozilla::dom::battery;
+
+namespace mozilla {
+namespace hal_impl {
+
+/**
+ * This is the declaration of UPowerClient class. This class is listening and
+ * communicating to upower daemon through DBus.
+ * There is no header file because this class shouldn't be public.
+ */
+class UPowerClient
+{
+public:
+  static UPowerClient* GetInstance();
+
+  void BeginListening();
+  void StopListening();
+
+  float GetLevel();
+  bool IsCharging();
+
+  ~UPowerClient();
+
+private:
+  UPowerClient();
+
+  enum States {
+    eState_Unknown = 0,
+    eState_Charging,
+    eState_Discharging,
+    eState_Empty,
+    eState_FullyCharged,
+    eState_PendingCharge,
+    eState_PendingDischarge
+  };
+
+  /**
+   * Update the currently tracked device.
+   * @return whether everything went ok.
+   */
+  void UpdateTrackedDevice();
+
+  /**
+   * Returns a hash table with the properties of aDevice.
+   * Note: the caller has to unref the hash table.
+   */
+  GHashTable* GetDeviceProperties(const gchar* aDevice);
+
+  /**
+   * Using the device properties (aHashTable), this method updates the member
+   * variable storing the values we care about.
+   */
+  void UpdateSavedInfo(GHashTable* aHashTable);
+
+  /**
+   * Callback used by 'DeviceChanged' signal.
+   */
+  static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath,
+                            UPowerClient* aListener);
+
+  /**
+   * Callback called when mDBusConnection gets a signal.
+   */
+  static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection,
+                                                  DBusMessage* aMessage,
+                                                  void* aData);
+
+  // The DBus connection object.
+  DBusGConnection* mDBusConnection;
+
+  // The DBus proxy object to upower.
+  DBusGProxy* mUPowerProxy;
+
+  // The path of the tracked device.
+  gchar* mTrackedDevice;
+
+  float mLevel;
+  bool mCharging;
+
+  static UPowerClient* sInstance;
+
+  static const guint sDeviceTypeBattery = 2;
+};
+
+/*
+ * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
+ *                   mozilla::hal_impl::DisableBatteryNotifications,
+ *               and mozilla::hal_impl::GetCurrentBatteryInformation.
+ */
+
+void
+EnableBatteryNotifications()
+{
+  UPowerClient::GetInstance()->BeginListening();
+}
+
+void
+DisableBatteryNotifications()
+{
+  UPowerClient::GetInstance()->StopListening();
+}
+
+void
+GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
+{
+  UPowerClient* upowerClient = UPowerClient::GetInstance();
+
+  aBatteryInfo->level() = upowerClient->GetLevel();
+  aBatteryInfo->charging() = upowerClient->IsCharging();
+}
+
+/*
+ * Following is the implementation of UPowerClient.
+ */
+
+UPowerClient* UPowerClient::sInstance = nsnull;
+
+/* static */ UPowerClient*
+UPowerClient::GetInstance()
+{
+  if (!sInstance) {
+    sInstance = new UPowerClient();
+  }
+
+  return sInstance;
+}
+
+UPowerClient::UPowerClient()
+  : mDBusConnection(nsnull)
+  , mUPowerProxy(nsnull)
+  , mTrackedDevice(nsnull)
+  , mLevel(kDefaultLevel)
+  , mCharging(kDefaultCharging)
+{
+}
+
+UPowerClient::~UPowerClient()
+{
+  NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice,
+               "The observers have not been correctly removed! "
+               "(StopListening should have been called)");
+}
+
+void
+UPowerClient::BeginListening()
+{
+  GError* error = nsnull;
+  mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+
+  if (!mDBusConnection) {
+    g_printerr("Failed to open connection to bus: %s\n", error->message);
+    g_error_free(error);
+    return;
+  }
+
+  DBusConnection* dbusConnection =
+    dbus_g_connection_get_connection(mDBusConnection);
+
+  // Make sure we do not exit the entire program if DBus connection get lost.
+  dbus_connection_set_exit_on_disconnect(dbusConnection, false);
+
+  // Listening to signals the DBus connection is going to get so we will know
+  // when it is lost and we will be able to disconnect cleanly.
+  dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this,
+                             nsnull);
+
+  mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection,
+                                           "org.freedesktop.UPower",
+                                           "/org/freedesktop/UPower",
+                                           "org.freedesktop.UPower");
+
+  UpdateTrackedDevice();
+
+  /*
+   * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals.
+   * If we do that, we would have to disconnect from those in StopListening.
+   * It's not yet implemented because it requires testing hot plugging and
+   * removal of a battery.
+   */
+  dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING,
+                          G_TYPE_INVALID);
+  dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged",
+                              G_CALLBACK (DeviceChanged), this, nsnull);
+}
+
+void
+UPowerClient::StopListening()
+{
+  // If mDBusConnection isn't initialized, that means we are not really listening.
+  if (!mDBusConnection) {
+    return;
+  }
+
+  dbus_connection_remove_filter(
+      dbus_g_connection_get_connection(mDBusConnection),
+      ConnectionSignalFilter, this);
+
+  dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged",
+                                 G_CALLBACK (DeviceChanged), this);
+
+  g_free(mTrackedDevice);
+  mTrackedDevice = nsnull;
+
+  g_object_unref(mUPowerProxy);
+  mUPowerProxy = nsnull;
+
+  dbus_g_connection_unref(mDBusConnection);
+  mDBusConnection = nsnull;
+
+  // We should now show the default values, not the latest we got.
+  mLevel = kDefaultLevel;
+  mCharging = kDefaultCharging;
+}
+
+void
+UPowerClient::UpdateTrackedDevice()
+{
+  GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray",
+                                                   DBUS_TYPE_G_OBJECT_PATH);
+  GPtrArray* devices = nsnull;
+  GError* error = nsnull;
+
+  // If that fails, that likely means upower isn't installed.
+  if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID,
+                         typeGPtrArray, &devices, G_TYPE_INVALID)) {
+    g_printerr ("Error: %s\n", error->message);
+
+    mTrackedDevice = nsnull;
+    g_error_free(error);
+    return;
+  }
+
+  /*
+   * We are looking for the first device that is a battery.
+   * TODO: we could try to combine more than one battery.
+   */
+  for (guint i=0; i<devices->len; ++i) {
+    gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i));
+    nsAutoRef<GHashTable> hashTable(GetDeviceProperties(devicePath));
+
+    if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) {
+      UpdateSavedInfo(hashTable);
+      mTrackedDevice = devicePath;
+      break;
+    }
+
+    g_free(devicePath);
+  }
+
+#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 22
+    g_ptr_array_unref(devices);
+#else
+    g_ptr_array_free(devices, true);
+#endif
+}
+
+/* static */ void
+UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener)
+{
+#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16
+  if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) {
+#else
+  if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) {
+#endif
+    return;
+  }
+
+  nsAutoRef<GHashTable> hashTable(aListener->GetDeviceProperties(aObjectPath));
+  aListener->UpdateSavedInfo(hashTable);
+
+  hal::NotifyBatteryChange(hal::BatteryInformation(aListener->mLevel, aListener->mCharging));
+}
+
+/* static */ DBusHandlerResult
+UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection,
+                                     DBusMessage* aMessage, void* aData)
+{
+  if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+    static_cast<UPowerClient*>(aData)->StopListening();
+    // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection
+    // might be shared and some other filters might want to do something.
+  }
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+GHashTable*
+UPowerClient::GetDeviceProperties(const gchar* aDevice)
+{
+  nsAutoRef<DBusGProxy> proxy(dbus_g_proxy_new_for_name(mDBusConnection,
+                                                        "org.freedesktop.UPower",
+                                                        aDevice,
+                                                        "org.freedesktop.DBus.Properties"));
+
+  GError* error = nsnull;
+  GHashTable* hashTable = nsnull;
+  GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
+                                            G_TYPE_VALUE);
+  if (!dbus_g_proxy_call(proxy, "GetAll", &error, G_TYPE_STRING,
+                         "org.freedesktop.UPower.Device", G_TYPE_INVALID,
+                         typeGHashTable, &hashTable, G_TYPE_INVALID)) {
+    g_printerr("Error: %s\n", error->message);
+    g_error_free(error);
+    return nsnull;
+  }
+
+  return hashTable;
+}
+
+void
+UPowerClient::UpdateSavedInfo(GHashTable* aHashTable)
+{
+  mLevel = g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage")))/100.f;
+
+  switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) {
+    case eState_Unknown:
+      mCharging = kDefaultCharging;
+      break;
+    case eState_Charging:
+    case eState_FullyCharged:
+    case eState_PendingCharge:
+      mCharging = true;
+      break;
+    case eState_Discharging:
+    case eState_Empty:
+    case eState_PendingDischarge:
+      mCharging = false;
+      break;
+  }
+}
+
+float
+UPowerClient::GetLevel()
+{
+  return mLevel;
+}
+
+bool
+UPowerClient::IsCharging()
+{
+  return mCharging;
+}
+
+} // namespace hal_impl
+} // namespace mozilla
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -180,21 +180,18 @@ ifdef MOZ_PLATFORM_MAEMO
 EXTRA_DSO_LDOPTS += $(MOZ_PLATFORM_MAEMO_LIBS)
 endif 
 
 ifdef MOZ_ENABLE_LIBCONIC
 EXTRA_DSO_LDOPTS += $(LIBCONIC_LIBS)
 endif
 
 ifdef MOZ_ENABLE_DBUS
-EXTRA_DSO_LDOPTS += $(MOZ_DBUS_LIBS)
-ifdef MOZ_PLATFORM_MAEMO
 EXTRA_DSO_LDOPTS += $(MOZ_DBUS_GLIB_LIBS)
 endif
-endif
 
 ifeq (gtk2,$(MOZ_WIDGET_TOOLKIT))
 EXTRA_DSO_LDOPTS += $(XLDFLAGS) $(XLIBS) $(XEXT_LIBS) $(XCOMPOSITE_LIBS) $(MOZ_PANGO_LIBS) $(MOZ_GTK2_LIBS) $(XT_LIBS) -lgthread-2.0
 EXTRA_DSO_LDOPTS += $(FT2_LIBS)
 endif
 
 ifeq (qt,$(MOZ_WIDGET_TOOLKIT))
 EXTRA_DSO_LDOPTS += $(XLDFLAGS) $(XLIBS) $(XT_LIBS) $(MOZ_QT_LIBS) -lgthread-2.0