UPowerClient.cpp (13256B)
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/. */ 5 6 #include "Hal.h" 7 #include "HalLog.h" 8 #include <mozilla/Attributes.h> 9 #include <mozilla/dom/battery/Constants.h> 10 #include "mozilla/GRefPtr.h" 11 #include "mozilla/GUniquePtr.h" 12 #include <cmath> 13 #include <gio/gio.h> 14 #include "mozilla/widget/AsyncDBus.h" 15 #include "nsAppShell.h" 16 17 using namespace mozilla::widget; 18 using namespace mozilla::dom::battery; 19 20 namespace mozilla::hal_impl { 21 22 /** 23 * This is the declaration of UPowerClient class. This class is listening and 24 * communicating to upower daemon through DBus. 25 * There is no header file because this class shouldn't be public. 26 */ 27 class UPowerClient { 28 public: 29 static UPowerClient* GetInstance(); 30 31 void BeginListening(); 32 void StopListening(); 33 34 double GetLevel(); 35 bool IsCharging(); 36 double GetRemainingTime(); 37 38 ~UPowerClient(); 39 40 private: 41 UPowerClient(); 42 43 enum States { 44 eState_Unknown = 0, 45 eState_Charging, 46 eState_Discharging, 47 eState_Empty, 48 eState_FullyCharged, 49 eState_PendingCharge, 50 eState_PendingDischarge 51 }; 52 53 /** 54 * Update the currently tracked device. 55 */ 56 void UpdateTrackedDevices(); 57 58 /** 59 * Update the battery info. 60 */ 61 bool GetBatteryInfo(); 62 63 /** 64 * Watch battery device for status 65 */ 66 bool AddTrackedDevice(const char* devicePath); 67 68 /** 69 * Callback used by 'DeviceChanged' signal. 70 */ 71 static void DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName, 72 gchar* aSignalName, GVariant* aParameters, 73 UPowerClient* aListener); 74 75 /** 76 * Callback used by 'PropertiesChanged' signal. 77 * This method is called when the the battery level changes. 78 * (Only with upower >= 0.99) 79 */ 80 static void DevicePropertiesChanged(GDBusProxy* aProxy, gchar* aSenderName, 81 gchar* aSignalName, GVariant* aParameters, 82 UPowerClient* aListener); 83 84 RefPtr<GCancellable> mCancellable; 85 86 // The DBus proxy object to upower. 87 RefPtr<GDBusProxy> mUPowerProxy; 88 89 // The path of the tracked device. 90 GUniquePtr<gchar> mTrackedDevice; 91 92 // The DBusGProxy for the tracked device. 93 RefPtr<GDBusProxy> mTrackedDeviceProxy; 94 95 double mLevel; 96 bool mCharging; 97 double mRemainingTime; 98 99 static UPowerClient* sInstance; 100 101 static const guint sDeviceTypeBattery = 2; 102 static const guint64 kUPowerUnknownRemainingTime = 0; 103 }; 104 105 /* 106 * Implementation of mozilla::hal_impl::EnableBatteryNotifications, 107 * mozilla::hal_impl::DisableBatteryNotifications, 108 * and mozilla::hal_impl::GetCurrentBatteryInformation. 109 */ 110 111 void EnableBatteryNotifications() { 112 UPowerClient::GetInstance()->BeginListening(); 113 } 114 115 void DisableBatteryNotifications() { 116 UPowerClient::GetInstance()->StopListening(); 117 } 118 119 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { 120 UPowerClient* upowerClient = UPowerClient::GetInstance(); 121 122 aBatteryInfo->level() = upowerClient->GetLevel(); 123 aBatteryInfo->charging() = upowerClient->IsCharging(); 124 aBatteryInfo->remainingTime() = upowerClient->GetRemainingTime(); 125 } 126 127 /* 128 * Following is the implementation of UPowerClient. 129 */ 130 131 UPowerClient* UPowerClient::sInstance = nullptr; 132 133 /* static */ 134 UPowerClient* UPowerClient::GetInstance() { 135 if (!sInstance) { 136 sInstance = new UPowerClient(); 137 } 138 139 return sInstance; 140 } 141 142 UPowerClient::UPowerClient() 143 : mLevel(kDefaultLevel), 144 mCharging(kDefaultCharging), 145 mRemainingTime(kDefaultRemainingTime) {} 146 147 UPowerClient::~UPowerClient() { 148 NS_ASSERTION( 149 !mUPowerProxy && !mTrackedDevice && !mTrackedDeviceProxy && !mCancellable, 150 "The observers have not been correctly removed! " 151 "(StopListening should have been called)"); 152 } 153 154 void UPowerClient::BeginListening() { 155 GUniquePtr<GError> error; 156 157 mCancellable = dont_AddRef(g_cancellable_new()); 158 CreateDBusProxyForBus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, 159 /* aInterfaceInfo = */ nullptr, 160 "org.freedesktop.UPower", "/org/freedesktop/UPower", 161 "org.freedesktop.UPower", mCancellable) 162 ->Then( 163 GetCurrentSerialEventTarget(), __func__, 164 // It's safe to capture this as we use mCancellable to stop 165 // listening. 166 [this](RefPtr<GDBusProxy>&& aProxy) { 167 mUPowerProxy = std::move(aProxy); 168 UpdateTrackedDevices(); 169 }, 170 [](GUniquePtr<GError>&& aError) { 171 if (!g_error_matches(aError.get(), G_IO_ERROR, 172 G_IO_ERROR_CANCELLED)) { 173 g_warning( 174 "Failed to create DBus proxy for org.freedesktop.UPower: " 175 "%s\n", 176 aError->message); 177 } 178 }); 179 } 180 181 void UPowerClient::StopListening() { 182 if (mUPowerProxy) { 183 g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged, 184 this); 185 } 186 if (mCancellable) { 187 g_cancellable_cancel(mCancellable); 188 mCancellable = nullptr; 189 } 190 191 mTrackedDeviceProxy = nullptr; 192 mTrackedDevice = nullptr; 193 mUPowerProxy = nullptr; 194 195 // We should now show the default values, not the latest we got. 196 mLevel = kDefaultLevel; 197 mCharging = kDefaultCharging; 198 mRemainingTime = kDefaultRemainingTime; 199 } 200 201 bool UPowerClient::AddTrackedDevice(const char* aDevicePath) { 202 nsAppShell::DBusConnectionCheck(); 203 RefPtr<GDBusProxy> proxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync( 204 G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, 205 "org.freedesktop.UPower", aDevicePath, "org.freedesktop.UPower.Device", 206 mCancellable, nullptr)); 207 if (!proxy) { 208 return false; 209 } 210 211 RefPtr<GVariant> deviceType = 212 dont_AddRef(g_dbus_proxy_get_cached_property(proxy, "Type")); 213 if (NS_WARN_IF(!deviceType || 214 !g_variant_is_of_type(deviceType, G_VARIANT_TYPE_UINT32))) { 215 return false; 216 } 217 218 if (g_variant_get_uint32(deviceType) != sDeviceTypeBattery) { 219 return false; 220 } 221 222 GUniquePtr<gchar> device(g_strdup(aDevicePath)); 223 mTrackedDevice = std::move(device); 224 mTrackedDeviceProxy = std::move(proxy); 225 226 if (!GetBatteryInfo()) { 227 return false; 228 } 229 hal::NotifyBatteryChange( 230 hal::BatteryInformation(mLevel, mCharging, mRemainingTime)); 231 232 g_signal_connect(mTrackedDeviceProxy, "g-signal", 233 G_CALLBACK(DevicePropertiesChanged), this); 234 return true; 235 } 236 237 void UPowerClient::UpdateTrackedDevices() { 238 // Reset the current tracked device: 239 g_signal_handlers_disconnect_by_func(mUPowerProxy, (void*)DeviceChanged, 240 this); 241 242 mTrackedDevice = nullptr; 243 mTrackedDeviceProxy = nullptr; 244 245 nsAppShell::DBusConnectionCheck(); 246 DBusProxyCall(mUPowerProxy, "EnumerateDevices", nullptr, 247 G_DBUS_CALL_FLAGS_NONE, -1, mCancellable) 248 ->Then( 249 GetCurrentSerialEventTarget(), __func__, 250 // It's safe to capture this as we use mCancellable to stop 251 // listening. 252 [this](RefPtr<GVariant>&& aResult) { 253 RefPtr<GVariant> variant = 254 dont_AddRef(g_variant_get_child_value(aResult.get(), 0)); 255 if (!variant || !g_variant_is_of_type( 256 variant, G_VARIANT_TYPE_OBJECT_PATH_ARRAY)) { 257 g_warning( 258 "Failed to enumerate devices of org.freedesktop.UPower: " 259 "wrong param %s\n", 260 g_variant_get_type_string(aResult.get())); 261 return; 262 } 263 gsize num = g_variant_n_children(variant); 264 for (gsize i = 0; i < num; i++) { 265 const char* devicePath = g_variant_get_string( 266 g_variant_get_child_value(variant, i), nullptr); 267 if (!devicePath) { 268 g_warning( 269 "Failed to enumerate devices of org.freedesktop.UPower: " 270 "missing device?\n"); 271 return; 272 } 273 /* 274 * We are looking for the first device that is a battery. 275 * TODO: we could try to combine more than one battery. 276 */ 277 if (AddTrackedDevice(devicePath)) { 278 break; 279 } 280 } 281 g_signal_connect(mUPowerProxy, "g-signal", 282 G_CALLBACK(DeviceChanged), this); 283 }, 284 [this](GUniquePtr<GError>&& aError) { 285 if (!g_error_matches(aError.get(), G_IO_ERROR, 286 G_IO_ERROR_CANCELLED)) { 287 g_warning( 288 "Failed to enumerate devices of org.freedesktop.UPower: %s\n", 289 aError->message); 290 } 291 g_signal_connect(mUPowerProxy, "g-signal", 292 G_CALLBACK(DeviceChanged), this); 293 }); 294 } 295 296 /* static */ 297 void UPowerClient::DeviceChanged(GDBusProxy* aProxy, gchar* aSenderName, 298 gchar* aSignalName, GVariant* aParameters, 299 UPowerClient* aListener) { 300 // Added new device. Act only if we're missing any tracked device 301 if (!g_strcmp0(aSignalName, "DeviceAdded")) { 302 if (aListener->mTrackedDevice) { 303 return; 304 } 305 } else if (!g_strcmp0(aSignalName, "DeviceRemoved")) { 306 if (g_strcmp0(aSenderName, aListener->mTrackedDevice.get())) { 307 return; 308 } 309 } 310 aListener->UpdateTrackedDevices(); 311 } 312 313 /* static */ 314 void UPowerClient::DevicePropertiesChanged(GDBusProxy* aProxy, 315 gchar* aSenderName, 316 gchar* aSignalName, 317 GVariant* aParameters, 318 UPowerClient* aListener) { 319 if (aListener->GetBatteryInfo()) { 320 hal::NotifyBatteryChange(hal::BatteryInformation( 321 sInstance->mLevel, sInstance->mCharging, sInstance->mRemainingTime)); 322 } 323 } 324 325 bool UPowerClient::GetBatteryInfo() { 326 bool isFull = false; 327 328 /* 329 * State values are confusing... 330 * First of all, after looking at upower sources (0.9.13), it seems that 331 * PendingDischarge and PendingCharge are not used. 332 * In addition, FullyCharged and Empty states are not clear because we do not 333 * know if the battery is actually charging or not. Those values come directly 334 * from sysfs (in the Linux kernel) which have four states: "Empty", "Full", 335 * "Charging" and "Discharging". In sysfs, "Empty" and "Full" are also only 336 * related to the level, not to the charging state. 337 * In this code, we are going to assume that Full means charging and Empty 338 * means discharging because if that is not the case, the state should not 339 * last a long time (actually, it should disappear at the following update). 340 * It might be even very hard to see real cases where the state is Empty and 341 * the battery is charging or the state is Full and the battery is discharging 342 * given that plugging/unplugging the battery should have an impact on the 343 * level. 344 */ 345 346 if (!mTrackedDeviceProxy) { 347 return false; 348 } 349 350 RefPtr<GVariant> value = dont_AddRef( 351 g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "State")); 352 if (NS_WARN_IF(!value || 353 !g_variant_is_of_type(value, G_VARIANT_TYPE_UINT32))) { 354 return false; 355 } 356 357 switch (g_variant_get_uint32(value)) { 358 case eState_Unknown: 359 mCharging = kDefaultCharging; 360 break; 361 case eState_FullyCharged: 362 isFull = true; 363 [[fallthrough]]; 364 case eState_Charging: 365 case eState_PendingCharge: 366 mCharging = true; 367 break; 368 case eState_Discharging: 369 case eState_Empty: 370 case eState_PendingDischarge: 371 mCharging = false; 372 break; 373 } 374 375 /* 376 * The battery level might be very close to 100% (like 99%) without 377 * increasing. It seems that upower sets the battery state as 'full' in that 378 * case so we should trust it and not even try to get the value. 379 */ 380 if (isFull) { 381 mLevel = 1.0; 382 } else { 383 value = dont_AddRef( 384 g_dbus_proxy_get_cached_property(mTrackedDeviceProxy, "Percentage")); 385 if (NS_WARN_IF(!value || 386 !g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE))) { 387 return false; 388 } 389 mLevel = round(g_variant_get_double(value)) * 0.01; 390 } 391 392 if (isFull) { 393 mRemainingTime = 0; 394 } else { 395 value = dont_AddRef(g_dbus_proxy_get_cached_property( 396 mTrackedDeviceProxy, mCharging ? "TimeToFull" : "TimeToEmpty")); 397 if (NS_WARN_IF(!value || 398 !g_variant_is_of_type(value, G_VARIANT_TYPE_INT64))) { 399 return false; 400 } 401 mRemainingTime = g_variant_get_int64(value); 402 if (mRemainingTime == kUPowerUnknownRemainingTime) { 403 mRemainingTime = kUnknownRemainingTime; 404 } 405 } 406 return true; 407 } 408 409 double UPowerClient::GetLevel() { return mLevel; } 410 411 bool UPowerClient::IsCharging() { return mCharging; } 412 413 double UPowerClient::GetRemainingTime() { return mRemainingTime; } 414 415 } // namespace mozilla::hal_impl