1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/linux/UPowerClient.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,471 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include <mozilla/Hal.h> 1.10 +#include <dbus/dbus-glib.h> 1.11 +#include <dbus/dbus-glib-lowlevel.h> 1.12 +#include <mozilla/dom/battery/Constants.h> 1.13 +#include "nsAutoRef.h" 1.14 + 1.15 +/* 1.16 + * Helper that manages the destruction of glib objects as soon as they leave 1.17 + * the current scope. 1.18 + * 1.19 + * We are specializing nsAutoRef class. 1.20 + */ 1.21 + 1.22 +template <> 1.23 +class nsAutoRefTraits<GHashTable> : public nsPointerRefTraits<GHashTable> 1.24 +{ 1.25 +public: 1.26 + static void Release(GHashTable* ptr) { g_hash_table_unref(ptr); } 1.27 +}; 1.28 + 1.29 +using namespace mozilla::dom::battery; 1.30 + 1.31 +namespace mozilla { 1.32 +namespace hal_impl { 1.33 + 1.34 +/** 1.35 + * This is the declaration of UPowerClient class. This class is listening and 1.36 + * communicating to upower daemon through DBus. 1.37 + * There is no header file because this class shouldn't be public. 1.38 + */ 1.39 +class UPowerClient 1.40 +{ 1.41 +public: 1.42 + static UPowerClient* GetInstance(); 1.43 + 1.44 + void BeginListening(); 1.45 + void StopListening(); 1.46 + 1.47 + double GetLevel(); 1.48 + bool IsCharging(); 1.49 + double GetRemainingTime(); 1.50 + 1.51 + ~UPowerClient(); 1.52 + 1.53 +private: 1.54 + UPowerClient(); 1.55 + 1.56 + enum States { 1.57 + eState_Unknown = 0, 1.58 + eState_Charging, 1.59 + eState_Discharging, 1.60 + eState_Empty, 1.61 + eState_FullyCharged, 1.62 + eState_PendingCharge, 1.63 + eState_PendingDischarge 1.64 + }; 1.65 + 1.66 + /** 1.67 + * Update the currently tracked device. 1.68 + * @return whether everything went ok. 1.69 + */ 1.70 + void UpdateTrackedDeviceSync(); 1.71 + 1.72 + /** 1.73 + * Returns a hash table with the properties of aDevice. 1.74 + * Note: the caller has to unref the hash table. 1.75 + */ 1.76 + GHashTable* GetDevicePropertiesSync(DBusGProxy* aProxy); 1.77 + void GetDevicePropertiesAsync(DBusGProxy* aProxy); 1.78 + static void GetDevicePropertiesCallback(DBusGProxy* aProxy, 1.79 + DBusGProxyCall* aCall, 1.80 + void* aData); 1.81 + 1.82 + /** 1.83 + * Using the device properties (aHashTable), this method updates the member 1.84 + * variable storing the values we care about. 1.85 + */ 1.86 + void UpdateSavedInfo(GHashTable* aHashTable); 1.87 + 1.88 + /** 1.89 + * Callback used by 'DeviceChanged' signal. 1.90 + */ 1.91 + static void DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, 1.92 + UPowerClient* aListener); 1.93 + 1.94 + /** 1.95 + * Callback called when mDBusConnection gets a signal. 1.96 + */ 1.97 + static DBusHandlerResult ConnectionSignalFilter(DBusConnection* aConnection, 1.98 + DBusMessage* aMessage, 1.99 + void* aData); 1.100 + 1.101 + // The DBus connection object. 1.102 + DBusGConnection* mDBusConnection; 1.103 + 1.104 + // The DBus proxy object to upower. 1.105 + DBusGProxy* mUPowerProxy; 1.106 + 1.107 + // The path of the tracked device. 1.108 + gchar* mTrackedDevice; 1.109 + 1.110 + // The DBusGProxy for the tracked device. 1.111 + DBusGProxy* mTrackedDeviceProxy; 1.112 + 1.113 + double mLevel; 1.114 + bool mCharging; 1.115 + double mRemainingTime; 1.116 + 1.117 + static UPowerClient* sInstance; 1.118 + 1.119 + static const guint sDeviceTypeBattery = 2; 1.120 + static const guint64 kUPowerUnknownRemainingTime = 0; 1.121 +}; 1.122 + 1.123 +/* 1.124 + * Implementation of mozilla::hal_impl::EnableBatteryNotifications, 1.125 + * mozilla::hal_impl::DisableBatteryNotifications, 1.126 + * and mozilla::hal_impl::GetCurrentBatteryInformation. 1.127 + */ 1.128 + 1.129 +void 1.130 +EnableBatteryNotifications() 1.131 +{ 1.132 + UPowerClient::GetInstance()->BeginListening(); 1.133 +} 1.134 + 1.135 +void 1.136 +DisableBatteryNotifications() 1.137 +{ 1.138 + UPowerClient::GetInstance()->StopListening(); 1.139 +} 1.140 + 1.141 +void 1.142 +GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) 1.143 +{ 1.144 + UPowerClient* upowerClient = UPowerClient::GetInstance(); 1.145 + 1.146 + aBatteryInfo->level() = upowerClient->GetLevel(); 1.147 + aBatteryInfo->charging() = upowerClient->IsCharging(); 1.148 + aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime(); 1.149 +} 1.150 + 1.151 +/* 1.152 + * Following is the implementation of UPowerClient. 1.153 + */ 1.154 + 1.155 +UPowerClient* UPowerClient::sInstance = nullptr; 1.156 + 1.157 +/* static */ UPowerClient* 1.158 +UPowerClient::GetInstance() 1.159 +{ 1.160 + if (!sInstance) { 1.161 + sInstance = new UPowerClient(); 1.162 + } 1.163 + 1.164 + return sInstance; 1.165 +} 1.166 + 1.167 +UPowerClient::UPowerClient() 1.168 + : mDBusConnection(nullptr) 1.169 + , mUPowerProxy(nullptr) 1.170 + , mTrackedDevice(nullptr) 1.171 + , mTrackedDeviceProxy(nullptr) 1.172 + , mLevel(kDefaultLevel) 1.173 + , mCharging(kDefaultCharging) 1.174 + , mRemainingTime(kDefaultRemainingTime) 1.175 +{ 1.176 +} 1.177 + 1.178 +UPowerClient::~UPowerClient() 1.179 +{ 1.180 + NS_ASSERTION(!mDBusConnection && !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy, 1.181 + "The observers have not been correctly removed! " 1.182 + "(StopListening should have been called)"); 1.183 +} 1.184 + 1.185 +void 1.186 +UPowerClient::BeginListening() 1.187 +{ 1.188 + GError* error = nullptr; 1.189 + mDBusConnection = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); 1.190 + 1.191 + if (!mDBusConnection) { 1.192 + g_printerr("Failed to open connection to bus: %s\n", error->message); 1.193 + g_error_free(error); 1.194 + return; 1.195 + } 1.196 + 1.197 + DBusConnection* dbusConnection = 1.198 + dbus_g_connection_get_connection(mDBusConnection); 1.199 + 1.200 + // Make sure we do not exit the entire program if DBus connection get lost. 1.201 + dbus_connection_set_exit_on_disconnect(dbusConnection, false); 1.202 + 1.203 + // Listening to signals the DBus connection is going to get so we will know 1.204 + // when it is lost and we will be able to disconnect cleanly. 1.205 + dbus_connection_add_filter(dbusConnection, ConnectionSignalFilter, this, 1.206 + nullptr); 1.207 + 1.208 + mUPowerProxy = dbus_g_proxy_new_for_name(mDBusConnection, 1.209 + "org.freedesktop.UPower", 1.210 + "/org/freedesktop/UPower", 1.211 + "org.freedesktop.UPower"); 1.212 + 1.213 + UpdateTrackedDeviceSync(); 1.214 + 1.215 + /* 1.216 + * TODO: we should probably listen to DeviceAdded and DeviceRemoved signals. 1.217 + * If we do that, we would have to disconnect from those in StopListening. 1.218 + * It's not yet implemented because it requires testing hot plugging and 1.219 + * removal of a battery. 1.220 + */ 1.221 + dbus_g_proxy_add_signal(mUPowerProxy, "DeviceChanged", G_TYPE_STRING, 1.222 + G_TYPE_INVALID); 1.223 + dbus_g_proxy_connect_signal(mUPowerProxy, "DeviceChanged", 1.224 + G_CALLBACK (DeviceChanged), this, nullptr); 1.225 +} 1.226 + 1.227 +void 1.228 +UPowerClient::StopListening() 1.229 +{ 1.230 + // If mDBusConnection isn't initialized, that means we are not really listening. 1.231 + if (!mDBusConnection) { 1.232 + return; 1.233 + } 1.234 + 1.235 + dbus_connection_remove_filter( 1.236 + dbus_g_connection_get_connection(mDBusConnection), 1.237 + ConnectionSignalFilter, this); 1.238 + 1.239 + dbus_g_proxy_disconnect_signal(mUPowerProxy, "DeviceChanged", 1.240 + G_CALLBACK (DeviceChanged), this); 1.241 + 1.242 + g_free(mTrackedDevice); 1.243 + mTrackedDevice = nullptr; 1.244 + 1.245 + if (mTrackedDeviceProxy) { 1.246 + g_object_unref(mTrackedDeviceProxy); 1.247 + mTrackedDeviceProxy = nullptr; 1.248 + } 1.249 + 1.250 + g_object_unref(mUPowerProxy); 1.251 + mUPowerProxy = nullptr; 1.252 + 1.253 + dbus_g_connection_unref(mDBusConnection); 1.254 + mDBusConnection = nullptr; 1.255 + 1.256 + // We should now show the default values, not the latest we got. 1.257 + mLevel = kDefaultLevel; 1.258 + mCharging = kDefaultCharging; 1.259 + mRemainingTime = kDefaultRemainingTime; 1.260 +} 1.261 + 1.262 +void 1.263 +UPowerClient::UpdateTrackedDeviceSync() 1.264 +{ 1.265 + GType typeGPtrArray = dbus_g_type_get_collection("GPtrArray", 1.266 + DBUS_TYPE_G_OBJECT_PATH); 1.267 + GPtrArray* devices = nullptr; 1.268 + GError* error = nullptr; 1.269 + 1.270 + // Reset the current tracked device: 1.271 + g_free(mTrackedDevice); 1.272 + mTrackedDevice = nullptr; 1.273 + 1.274 + // Reset the current tracked device proxy: 1.275 + if (mTrackedDeviceProxy) { 1.276 + g_object_unref(mTrackedDeviceProxy); 1.277 + mTrackedDeviceProxy = nullptr; 1.278 + } 1.279 + 1.280 + // If that fails, that likely means upower isn't installed. 1.281 + if (!dbus_g_proxy_call(mUPowerProxy, "EnumerateDevices", &error, G_TYPE_INVALID, 1.282 + typeGPtrArray, &devices, G_TYPE_INVALID)) { 1.283 + g_printerr ("Error: %s\n", error->message); 1.284 + g_error_free(error); 1.285 + return; 1.286 + } 1.287 + 1.288 + /* 1.289 + * We are looking for the first device that is a battery. 1.290 + * TODO: we could try to combine more than one battery. 1.291 + */ 1.292 + for (guint i=0; i<devices->len; ++i) { 1.293 + gchar* devicePath = static_cast<gchar*>(g_ptr_array_index(devices, i)); 1.294 + 1.295 + DBusGProxy* proxy = dbus_g_proxy_new_from_proxy(mUPowerProxy, 1.296 + "org.freedesktop.DBus.Properties", 1.297 + devicePath); 1.298 + 1.299 + nsAutoRef<GHashTable> hashTable(GetDevicePropertiesSync(proxy)); 1.300 + 1.301 + if (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(hashTable, "Type"))) == sDeviceTypeBattery) { 1.302 + UpdateSavedInfo(hashTable); 1.303 + mTrackedDevice = devicePath; 1.304 + mTrackedDeviceProxy = proxy; 1.305 + break; 1.306 + } 1.307 + 1.308 + g_object_unref(proxy); 1.309 + g_free(devicePath); 1.310 + } 1.311 + 1.312 + g_ptr_array_free(devices, true); 1.313 +} 1.314 + 1.315 +/* static */ void 1.316 +UPowerClient::DeviceChanged(DBusGProxy* aProxy, const gchar* aObjectPath, UPowerClient* aListener) 1.317 +{ 1.318 + if (!aListener->mTrackedDevice) { 1.319 + return; 1.320 + } 1.321 + 1.322 +#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 16 1.323 + if (g_strcmp0(aObjectPath, aListener->mTrackedDevice)) { 1.324 +#else 1.325 + if (g_ascii_strcasecmp(aObjectPath, aListener->mTrackedDevice)) { 1.326 +#endif 1.327 + return; 1.328 + } 1.329 + 1.330 + aListener->GetDevicePropertiesAsync(aListener->mTrackedDeviceProxy); 1.331 +} 1.332 + 1.333 +/* static */ DBusHandlerResult 1.334 +UPowerClient::ConnectionSignalFilter(DBusConnection* aConnection, 1.335 + DBusMessage* aMessage, void* aData) 1.336 +{ 1.337 + if (dbus_message_is_signal(aMessage, DBUS_INTERFACE_LOCAL, "Disconnected")) { 1.338 + static_cast<UPowerClient*>(aData)->StopListening(); 1.339 + // We do not return DBUS_HANDLER_RESULT_HANDLED here because the connection 1.340 + // might be shared and some other filters might want to do something. 1.341 + } 1.342 + 1.343 + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 1.344 +} 1.345 + 1.346 +GHashTable* 1.347 +UPowerClient::GetDevicePropertiesSync(DBusGProxy* aProxy) 1.348 +{ 1.349 + GError* error = nullptr; 1.350 + GHashTable* hashTable = nullptr; 1.351 + GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, 1.352 + G_TYPE_VALUE); 1.353 + if (!dbus_g_proxy_call(aProxy, "GetAll", &error, G_TYPE_STRING, 1.354 + "org.freedesktop.UPower.Device", G_TYPE_INVALID, 1.355 + typeGHashTable, &hashTable, G_TYPE_INVALID)) { 1.356 + g_printerr("Error: %s\n", error->message); 1.357 + g_error_free(error); 1.358 + return nullptr; 1.359 + } 1.360 + 1.361 + return hashTable; 1.362 +} 1.363 + 1.364 +/* static */ void 1.365 +UPowerClient::GetDevicePropertiesCallback(DBusGProxy* aProxy, 1.366 + DBusGProxyCall* aCall, void* aData) 1.367 +{ 1.368 + GError* error = nullptr; 1.369 + GHashTable* hashTable = nullptr; 1.370 + GType typeGHashTable = dbus_g_type_get_map("GHashTable", G_TYPE_STRING, 1.371 + G_TYPE_VALUE); 1.372 + if (!dbus_g_proxy_end_call(aProxy, aCall, &error, typeGHashTable, 1.373 + &hashTable, G_TYPE_INVALID)) { 1.374 + g_printerr("Error: %s\n", error->message); 1.375 + g_error_free(error); 1.376 + } else { 1.377 + sInstance->UpdateSavedInfo(hashTable); 1.378 + hal::NotifyBatteryChange(hal::BatteryInformation(sInstance->mLevel, 1.379 + sInstance->mCharging, 1.380 + sInstance->mRemainingTime)); 1.381 + g_hash_table_unref(hashTable); 1.382 + } 1.383 +} 1.384 + 1.385 +void 1.386 +UPowerClient::GetDevicePropertiesAsync(DBusGProxy* aProxy) 1.387 +{ 1.388 + dbus_g_proxy_begin_call(aProxy, "GetAll", GetDevicePropertiesCallback, nullptr, 1.389 + nullptr, G_TYPE_STRING, 1.390 + "org.freedesktop.UPower.Device", G_TYPE_INVALID); 1.391 +} 1.392 + 1.393 +void 1.394 +UPowerClient::UpdateSavedInfo(GHashTable* aHashTable) 1.395 +{ 1.396 + bool isFull = false; 1.397 + 1.398 + /* 1.399 + * State values are confusing... 1.400 + * First of all, after looking at upower sources (0.9.13), it seems that 1.401 + * PendingDischarge and PendingCharge are not used. 1.402 + * In addition, FullyCharged and Empty states are not clear because we do not 1.403 + * know if the battery is actually charging or not. Those values come directly 1.404 + * from sysfs (in the Linux kernel) which have four states: "Empty", "Full", 1.405 + * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only 1.406 + * related to the level, not to the charging state. 1.407 + * In this code, we are going to assume that Full means charging and Empty 1.408 + * means discharging because if that is not the case, the state should not 1.409 + * last a long time (actually, it should disappear at the following update). 1.410 + * It might be even very hard to see real cases where the state is Empty and 1.411 + * the battery is charging or the state is Full and the battery is discharging 1.412 + * given that plugging/unplugging the battery should have an impact on the 1.413 + * level. 1.414 + */ 1.415 + switch (g_value_get_uint(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "State")))) { 1.416 + case eState_Unknown: 1.417 + mCharging = kDefaultCharging; 1.418 + break; 1.419 + case eState_FullyCharged: 1.420 + isFull = true; 1.421 + case eState_Charging: 1.422 + case eState_PendingCharge: 1.423 + mCharging = true; 1.424 + break; 1.425 + case eState_Discharging: 1.426 + case eState_Empty: 1.427 + case eState_PendingDischarge: 1.428 + mCharging = false; 1.429 + break; 1.430 + } 1.431 + 1.432 + /* 1.433 + * The battery level might be very close to 100% (like 99.xxxx%) without 1.434 + * increasing. It seems that upower sets the battery state as 'full' in that 1.435 + * case so we should trust it and not even try to get the value. 1.436 + */ 1.437 + if (isFull) { 1.438 + mLevel = 1.0; 1.439 + } else { 1.440 + mLevel = g_value_get_double(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "Percentage")))*0.01; 1.441 + } 1.442 + 1.443 + if (isFull) { 1.444 + mRemainingTime = 0; 1.445 + } else { 1.446 + mRemainingTime = mCharging ? g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToFull"))) 1.447 + : g_value_get_int64(static_cast<const GValue*>(g_hash_table_lookup(aHashTable, "TimeToEmpty"))); 1.448 + 1.449 + if (mRemainingTime == kUPowerUnknownRemainingTime) { 1.450 + mRemainingTime = kUnknownRemainingTime; 1.451 + } 1.452 + } 1.453 +} 1.454 + 1.455 +double 1.456 +UPowerClient::GetLevel() 1.457 +{ 1.458 + return mLevel; 1.459 +} 1.460 + 1.461 +bool 1.462 +UPowerClient::IsCharging() 1.463 +{ 1.464 + return mCharging; 1.465 +} 1.466 + 1.467 +double 1.468 +UPowerClient::GetRemainingTime() 1.469 +{ 1.470 + return mRemainingTime; 1.471 +} 1.472 + 1.473 +} // namespace hal_impl 1.474 +} // namespace mozilla