tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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