michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "nsAutoRef.h" michael@0: michael@0: /* michael@0: * Helper that manages the destruction of glib objects as soon as they leave michael@0: * the current scope. michael@0: * michael@0: * We are specializing nsAutoRef class. michael@0: */ michael@0: michael@0: template <> michael@0: class nsAutoRefTraits : public nsPointerRefTraits michael@0: { michael@0: public: michael@0: static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); } michael@0: }; michael@0: michael@0: using namespace mozilla::dom::battery; michael@0: michael@0: namespace mozilla { michael@0: namespace hal_impl { michael@0: michael@0: /** michael@0: * This is the declaration of UPowerClient class. This class is listening and michael@0: * communicating to upower daemon through DBus. michael@0: * There is no header file because this class shouldn't be public. michael@0: */ michael@0: class UPowerClient michael@0: { michael@0: public: michael@0: static UPowerClient* GetInstance(); michael@0: michael@0: void BeginListening(); michael@0: void StopListening(); michael@0: michael@0: double GetLevel(); michael@0: bool IsCharging(); michael@0: double GetRemainingTime(); michael@0: michael@0: ~UPowerClient(); michael@0: michael@0: private: michael@0: UPowerClient(); michael@0: michael@0: enum States { michael@0: eState_Unknown = 0, michael@0: eState_Charging, michael@0: eState_Discharging, michael@0: eState_Empty, michael@0: eState_FullyCharged, michael@0: eState_PendingCharge, michael@0: eState_PendingDischarge michael@0: }; michael@0: michael@0: /** michael@0: * Update the currently tracked device. michael@0: * @return whether everything went ok. michael@0: */ michael@0: void UpdateTrackedDeviceSync(); michael@0: michael@0: /** michael@0: * Returns a hash table with the properties of aDevice. michael@0: * Note: the caller has to unref the hash table. michael@0: */ michael@0: GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy); michael@0: void GetDevicePropertiesAsync(DBusGProxy* aProxy); michael@0: static void GetDevicePropertiesCallback(DBusGProxy* aProxy, michael@0: DBusGProxyCall* aCall, michael@0: void* aData); michael@0: michael@0: /** michael@0: * Using the device properties (aHashTable), this method updates the member michael@0: * variable storing the values we care about. michael@0: */ michael@0: void UpdateSavedInfo(GHashTable* aHashTable); michael@0: michael@0: /** michael@0: * Callback used by 'DeviceChanged' signal. michael@0: */ michael@0: static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, michael@0: UPowerClient* aListener); michael@0: michael@0: /** michael@0: * Callback called when mDBusConnection gets a signal. michael@0: */ michael@0: static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection, michael@0: DBusMessage* aMessage, michael@0: void* aData); michael@0: michael@0: // The DBus connection object. michael@0: DBusGConnection* mDBusConnection; michael@0: michael@0: // The DBus proxy object to upower. michael@0: DBusGProxy* mUPowerProxy; michael@0: michael@0: // The path of the tracked device. michael@0: gchar* mTrackedDevice; michael@0: michael@0: // The DBusGProxy for the tracked device. michael@0: DBusGProxy* mTrackedDeviceProxy; michael@0: michael@0: double mLevel; michael@0: bool mCharging; michael@0: double mRemainingTime; michael@0: michael@0: static UPowerClient* sInstance; michael@0: michael@0: static const guint sDeviceTypeBattery = 2; michael@0: static const guint64 kUPowerUnknownRemainingTime = 0; michael@0: }; michael@0: michael@0: /* michael@0: * Implementation of mozilla::hal_impl::EnableBatteryNotifications, michael@0: * mozilla::hal_impl::DisableBatteryNotifications, michael@0: * and mozilla::hal_impl::GetCurrentBatteryInformation. michael@0: */ michael@0: michael@0: void michael@0: EnableBatteryNotifications() michael@0: { michael@0: UPowerClient::GetInstance()->BeginListening(); michael@0: } michael@0: michael@0: void michael@0: DisableBatteryNotifications() michael@0: { michael@0: UPowerClient::GetInstance()->StopListening(); michael@0: } michael@0: michael@0: void michael@0: GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) michael@0: { michael@0: UPowerClient* upowerClient = UPowerClient::GetInstance(); michael@0: michael@0: aBatteryInfo->level() = upowerClient->GetLevel(); michael@0: aBatteryInfo->charging() = upowerClient->IsCharging(); michael@0: aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime(); michael@0: } michael@0: michael@0: /* michael@0: * Following is the implementation of UPowerClient. michael@0: */ michael@0: michael@0: UPowerClient* UPowerClient::sInstance = nullptr; michael@0: michael@0: /* static */ UPowerClient* michael@0: UPowerClient::GetInstance() michael@0: { michael@0: if (!sInstance) { michael@0: sInstance = new UPowerClient(); michael@0: } michael@0: michael@0: return sInstance; michael@0: } michael@0: michael@0: UPowerClient::UPowerClient() michael@0: : mDBusConnection(nullptr) michael@0: , mUPowerProxy(nullptr) michael@0: , mTrackedDevice(nullptr) michael@0: , mTrackedDeviceProxy(nullptr) michael@0: , mLevel(kDefaultLevel) michael@0: , mCharging(kDefaultCharging) michael@0: , mRemainingTime(kDefaultRemainingTime) michael@0: { michael@0: } michael@0: michael@0: UPowerClient::~UPowerClient() michael@0: { michael@0: NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy, michael@0: "The observers have not been correctly removed! " michael@0: "(StopListening should have been called)"); michael@0: } michael@0: michael@0: void michael@0: UPowerClient::BeginListening() michael@0: { michael@0: GError* error = nullptr; michael@0: mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); michael@0: michael@0: if (!mDBusConnection) { michael@0: g_printerr("Failed to open connection to bus: %s\n", error->message); michael@0: g_error_free(error); michael@0: return; michael@0: } michael@0: michael@0: DBusConnection* dbusConnection = michael@0: dbus_g_connection_get_connection(mDBusConnection); michael@0: michael@0: // Make sure we do not exit the entire program if DBus connection get lost. michael@0: dbus_connection_set_exit_on_disconnect(dbusConnection, false); michael@0: michael@0: // Listening to signals the DBus connection is going to get so we will know michael@0: // when it is lost and we will be able to disconnect cleanly. michael@0: dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this, michael@0: nullptr); michael@0: michael@0: mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection, michael@0: "org.freedesktop.UPower", michael@0: "/org/freedesktop/UPower", michael@0: "org.freedesktop.UPower"); michael@0: michael@0: UpdateTrackedDeviceSync(); michael@0: michael@0: /* michael@0: * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals. michael@0: * If we do that, we would have to disconnect from those in StopListening. michael@0: * It's not yet implemented because it requires testing hot plugging and michael@0: * removal of a battery. michael@0: */ michael@0: dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING, michael@0: G_TYPE_INVALID); michael@0: dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged", michael@0: G_CALLBACK (DeviceChanged), this, nullptr); michael@0: } michael@0: michael@0: void michael@0: UPowerClient::StopListening() michael@0: { michael@0: // If mDBusConnection isn't initialized, that means we are not really listening. michael@0: if (!mDBusConnection) { michael@0: return; michael@0: } michael@0: michael@0: dbus_connection_remove_filter( michael@0: dbus_g_connection_get_connection(mDBusConnection), michael@0: ConnectionSignalFilter, this); michael@0: michael@0: dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged", michael@0: G_CALLBACK (DeviceChanged), this); michael@0: michael@0: g_free(mTrackedDevice); michael@0: mTrackedDevice = nullptr; michael@0: michael@0: if (mTrackedDeviceProxy) { michael@0: g_object_unref(mTrackedDeviceProxy); michael@0: mTrackedDeviceProxy = nullptr; michael@0: } michael@0: michael@0: g_object_unref(mUPowerProxy); michael@0: mUPowerProxy = nullptr; michael@0: michael@0: dbus_g_connection_unref(mDBusConnection); michael@0: mDBusConnection = nullptr; michael@0: michael@0: // We should now show the default values, not the latest we got. michael@0: mLevel = kDefaultLevel; michael@0: mCharging = kDefaultCharging; michael@0: mRemainingTime = kDefaultRemainingTime; michael@0: } michael@0: michael@0: void michael@0: UPowerClient::UpdateTrackedDeviceSync() michael@0: { michael@0: GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray", michael@0: DBUS_TYPE_G_OBJECT_PATH); michael@0: GPtrArray* devices = nullptr; michael@0: GError* error = nullptr; michael@0: michael@0: // Reset the current tracked device: michael@0: g_free(mTrackedDevice); michael@0: mTrackedDevice = nullptr; michael@0: michael@0: // Reset the current tracked device proxy: michael@0: if (mTrackedDeviceProxy) { michael@0: g_object_unref(mTrackedDeviceProxy); michael@0: mTrackedDeviceProxy = nullptr; michael@0: } michael@0: michael@0: // If that fails, that likely means upower isn't installed. michael@0: if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID, michael@0: typeGPtrArray, &devices, G_TYPE_INVALID)) { michael@0: g_printerr ("Error: %s\n", error->message); michael@0: g_error_free(error); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * We are looking for the first device that is a battery. michael@0: * TODO: we could try to combine more than one battery. michael@0: */ michael@0: for (guint i=0; ilen; ++i) { michael@0: gchar* devicePath = static_cast(g_ptr_array_index(devices, i)); michael@0: michael@0: DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy, michael@0: "org.freedesktop.DBus.Properties", michael@0: devicePath); michael@0: michael@0: nsAutoRef hashTable(GetDevicePropertiesSync(proxy)); michael@0: michael@0: if (g_value_get_uint(static_cast(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) { michael@0: UpdateSavedInfo(hashTable); michael@0: mTrackedDevice = devicePath; michael@0: mTrackedDeviceProxy = proxy; michael@0: break; michael@0: } michael@0: michael@0: g_object_unref(proxy); michael@0: g_free(devicePath); michael@0: } michael@0: michael@0: g_ptr_array_free(devices, true); michael@0: } michael@0: michael@0: /* static */ void michael@0: UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener) michael@0: { michael@0: if (!aListener->mTrackedDevice) { michael@0: return; michael@0: } michael@0: michael@0: #if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16 michael@0: if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) { michael@0: #else michael@0: if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) { michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy); michael@0: } michael@0: michael@0: /* static */ DBusHandlerResult michael@0: UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection, michael@0: DBusMessage* aMessage, void* aData) michael@0: { michael@0: if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) { michael@0: static_cast(aData)->StopListening(); michael@0: // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection michael@0: // might be shared and some other filters might want to do something. michael@0: } michael@0: michael@0: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; michael@0: } michael@0: michael@0: GHashTable* michael@0: UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy) michael@0: { michael@0: GError* error = nullptr; michael@0: GHashTable* hashTable = nullptr; michael@0: GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, michael@0: G_TYPE_VALUE); michael@0: if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING, michael@0: "org.freedesktop.UPower.Device", G_TYPE_INVALID, michael@0: typeGHashTable, &hashTable, G_TYPE_INVALID)) { michael@0: g_printerr("Error: %s\n", error->message); michael@0: g_error_free(error); michael@0: return nullptr; michael@0: } michael@0: michael@0: return hashTable; michael@0: } michael@0: michael@0: /* static */ void michael@0: UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy, michael@0: DBusGProxyCall* aCall, void* aData) michael@0: { michael@0: GError* error = nullptr; michael@0: GHashTable* hashTable = nullptr; michael@0: GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, michael@0: G_TYPE_VALUE); michael@0: if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable, michael@0: &hashTable, G_TYPE_INVALID)) { michael@0: g_printerr("Error: %s\n", error->message); michael@0: g_error_free(error); michael@0: } else { michael@0: sInstance->UpdateSavedInfo(hashTable); michael@0: hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel, michael@0: sInstance->mCharging, michael@0: sInstance->mRemainingTime)); michael@0: g_hash_table_unref(hashTable); michael@0: } michael@0: } michael@0: michael@0: void michael@0: UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) michael@0: { michael@0: dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr, michael@0: nullptr, G_TYPE_STRING, michael@0: "org.freedesktop.UPower.Device", G_TYPE_INVALID); michael@0: } michael@0: michael@0: void michael@0: UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) michael@0: { michael@0: bool isFull = false; michael@0: michael@0: /* michael@0: * State values are confusing... michael@0: * First of all, after looking at upower sources (0.9.13), it seems that michael@0: * PendingDischarge and PendingCharge are not used. michael@0: * In addition, FullyCharged and Empty states are not clear because we do not michael@0: * know if the battery is actually charging or not. Those values come directly michael@0: * from sysfs (in the Linux kernel) which have four states: "Empty", "Full", michael@0: * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only michael@0: * related to the level, not to the charging state. michael@0: * In this code, we are going to assume that Full means charging and Empty michael@0: * means discharging because if that is not the case, the state should not michael@0: * last a long time (actually, it should disappear at the following update). michael@0: * It might be even very hard to see real cases where the state is Empty and michael@0: * the battery is charging or the state is Full and the battery is discharging michael@0: * given that plugging/unplugging the battery should have an impact on the michael@0: * level. michael@0: */ michael@0: switch (g_value_get_uint(static_cast(g_hash_table_lookup(aHashTable, "State")))) { michael@0: case eState_Unknown: michael@0: mCharging = kDefaultCharging; michael@0: break; michael@0: case eState_FullyCharged: michael@0: isFull = true; michael@0: case eState_Charging: michael@0: case eState_PendingCharge: michael@0: mCharging = true; michael@0: break; michael@0: case eState_Discharging: michael@0: case eState_Empty: michael@0: case eState_PendingDischarge: michael@0: mCharging = false; michael@0: break; michael@0: } michael@0: michael@0: /* michael@0: * The battery level might be very close to 100% (like 99.xxxx%) without michael@0: * increasing. It seems that upower sets the battery state as 'full' in that michael@0: * case so we should trust it and not even try to get the value. michael@0: */ michael@0: if (isFull) { michael@0: mLevel = 1.0; michael@0: } else { michael@0: mLevel = g_value_get_double(static_cast(g_hash_table_lookup(aHashTable, "Percentage")))*0.01; michael@0: } michael@0: michael@0: if (isFull) { michael@0: mRemainingTime = 0; michael@0: } else { michael@0: mRemainingTime = mCharging ? g_value_get_int64(static_cast(g_hash_table_lookup(aHashTable, "TimeToFull"))) michael@0: : g_value_get_int64(static_cast(g_hash_table_lookup(aHashTable, "TimeToEmpty"))); michael@0: michael@0: if (mRemainingTime == kUPowerUnknownRemainingTime) { michael@0: mRemainingTime = kUnknownRemainingTime; michael@0: } michael@0: } michael@0: } michael@0: michael@0: double michael@0: UPowerClient::GetLevel() michael@0: { michael@0: return mLevel; michael@0: } michael@0: michael@0: bool michael@0: UPowerClient::IsCharging() michael@0: { michael@0: return mCharging; michael@0: } michael@0: michael@0: double michael@0: UPowerClient::GetRemainingTime() michael@0: { michael@0: return mRemainingTime; michael@0: } michael@0: michael@0: } // namespace hal_impl michael@0: } // namespace mozilla