tor-browser

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

CocoaBattery.cpp (9799B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #import <CoreFoundation/CoreFoundation.h>
      8 #import <IOKit/ps/IOPowerSources.h>
      9 #import <IOKit/ps/IOPSKeys.h>
     10 
     11 #include <mozilla/Hal.h>
     12 #include <mozilla/dom/battery/Constants.h>
     13 #include <mozilla/Services.h>
     14 
     15 #include <nsIObserverService.h>
     16 #include <nsIObserver.h>
     17 
     18 #include <dlfcn.h>
     19 
     20 #define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit"
     21 
     22 #ifndef kIOPSTimeRemainingUnknown
     23 #  define kIOPSTimeRemainingUnknown ((CFTimeInterval) - 1.0)
     24 #endif
     25 #ifndef kIOPSTimeRemainingUnlimited
     26 #  define kIOPSTimeRemainingUnlimited ((CFTimeInterval) - 2.0)
     27 #endif
     28 
     29 using namespace mozilla::dom::battery;
     30 
     31 namespace mozilla {
     32 namespace hal_impl {
     33 
     34 typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void);
     35 
     36 class MacPowerInformationService {
     37 public:
     38  static MacPowerInformationService* GetInstance();
     39  static void Shutdown();
     40  static bool IsShuttingDown();
     41 
     42  void BeginListening();
     43  void StopListening();
     44 
     45  static void HandleChange(void* aContext);
     46 
     47  ~MacPowerInformationService();
     48 
     49 private:
     50  MacPowerInformationService();
     51 
     52  // The reference to the runloop that is notified of power changes.
     53  CFRunLoopSourceRef mRunLoopSource;
     54 
     55  double mLevel;
     56  bool mCharging;
     57  double mRemainingTime;
     58  bool mShouldNotify;
     59 
     60  friend void GetCurrentBatteryInformation(
     61      hal::BatteryInformation* aBatteryInfo);
     62 
     63  static MacPowerInformationService* sInstance;
     64  static bool sShuttingDown;
     65 
     66  static void* sIOKitFramework;
     67  static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate;
     68 };
     69 
     70 void* MacPowerInformationService::sIOKitFramework;
     71 IOPSGetTimeRemainingEstimateFunc
     72    MacPowerInformationService::sIOPSGetTimeRemainingEstimate;
     73 
     74 /*
     75 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
     76 *                   mozilla::hal_impl::DisableBatteryNotifications,
     77 *               and mozilla::hal_impl::GetCurrentBatteryInformation.
     78 */
     79 
     80 void EnableBatteryNotifications() {
     81  if (!MacPowerInformationService::IsShuttingDown()) {
     82    MacPowerInformationService::GetInstance()->BeginListening();
     83  }
     84 }
     85 
     86 void DisableBatteryNotifications() {
     87  if (!MacPowerInformationService::IsShuttingDown()) {
     88    MacPowerInformationService::GetInstance()->StopListening();
     89  }
     90 }
     91 
     92 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) {
     93  MacPowerInformationService* powerService =
     94      MacPowerInformationService::GetInstance();
     95 
     96  aBatteryInfo->level() = powerService->mLevel;
     97  aBatteryInfo->charging() = powerService->mCharging;
     98  aBatteryInfo->remainingTime() = powerService->mRemainingTime;
     99 }
    100 
    101 bool MacPowerInformationService::sShuttingDown = false;
    102 
    103 /*
    104 * Following is the implementation of MacPowerInformationService.
    105 */
    106 
    107 MacPowerInformationService* MacPowerInformationService::sInstance = nullptr;
    108 
    109 namespace {
    110 struct SingletonDestroyer final : public nsIObserver {
    111  NS_DECL_ISUPPORTS
    112  NS_DECL_NSIOBSERVER
    113 
    114 private:
    115  ~SingletonDestroyer() {}
    116 };
    117 
    118 NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver)
    119 
    120 NS_IMETHODIMP
    121 SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) {
    122  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
    123  MacPowerInformationService::Shutdown();
    124  return NS_OK;
    125 }
    126 }  // namespace
    127 
    128 /* static */
    129 MacPowerInformationService* MacPowerInformationService::GetInstance() {
    130  if (sInstance) {
    131    return sInstance;
    132  }
    133 
    134  sInstance = new MacPowerInformationService();
    135 
    136  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    137  if (obs) {
    138    obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
    139  }
    140 
    141  return sInstance;
    142 }
    143 
    144 bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; }
    145 
    146 void MacPowerInformationService::Shutdown() {
    147  sShuttingDown = true;
    148  delete sInstance;
    149  sInstance = nullptr;
    150 }
    151 
    152 MacPowerInformationService::MacPowerInformationService()
    153    : mRunLoopSource(nullptr),
    154      mLevel(kDefaultLevel),
    155      mCharging(kDefaultCharging),
    156      mRemainingTime(kDefaultRemainingTime),
    157      mShouldNotify(false) {
    158  // IOPSGetTimeRemainingEstimate (and the related constants) are only available
    159  // on 10.7, so we test for their presence at runtime.
    160  sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL);
    161  if (sIOKitFramework) {
    162    sIOPSGetTimeRemainingEstimate = (IOPSGetTimeRemainingEstimateFunc)dlsym(
    163        sIOKitFramework, "IOPSGetTimeRemainingEstimate");
    164  } else {
    165    sIOPSGetTimeRemainingEstimate = nullptr;
    166  }
    167 }
    168 
    169 MacPowerInformationService::~MacPowerInformationService() {
    170  MOZ_ASSERT(!mRunLoopSource,
    171             "The observers have not been correctly removed! "
    172             "(StopListening should have been called)");
    173 
    174  if (sIOKitFramework) {
    175    dlclose(sIOKitFramework);
    176  }
    177 }
    178 
    179 void MacPowerInformationService::BeginListening() {
    180  // Set ourselves up to be notified about changes.
    181  MOZ_ASSERT(!mRunLoopSource,
    182             "IOPS Notification Loop Source already set up. "
    183             "(StopListening should have been called)");
    184 
    185  mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this);
    186  if (mRunLoopSource) {
    187    ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource,
    188                         kCFRunLoopDefaultMode);
    189 
    190    // Invoke our callback now so we have data if GetCurrentBatteryInformation
    191    // is called before a change happens.
    192    HandleChange(this);
    193    mShouldNotify = true;
    194  }
    195 }
    196 
    197 void MacPowerInformationService::StopListening() {
    198  if (mRunLoopSource) {
    199    ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource,
    200                            kCFRunLoopDefaultMode);
    201    mRunLoopSource = nullptr;
    202  }
    203 }
    204 
    205 void MacPowerInformationService::HandleChange(void* aContext) {
    206  MacPowerInformationService* power =
    207      static_cast<MacPowerInformationService*>(aContext);
    208 
    209  CFTypeRef data = ::IOPSCopyPowerSourcesInfo();
    210  if (!data) {
    211    ::CFRelease(data);
    212    return;
    213  }
    214 
    215  // Get the list of power sources.
    216  CFArrayRef list = ::IOPSCopyPowerSourcesList(data);
    217  if (!list) {
    218    ::CFRelease(list);
    219    return;
    220  }
    221 
    222  // Default values. These will be used if there are 0 sources or we can't find
    223  // better information.
    224  double level = kDefaultLevel;
    225  double charging = kDefaultCharging;
    226  double remainingTime = kDefaultRemainingTime;
    227 
    228  // Look for the first battery power source to give us the information we need.
    229  // Usually there's only 1 available, depending on current power source.
    230  for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) {
    231    CFTypeRef source = ::CFArrayGetValueAtIndex(list, i);
    232    CFDictionaryRef currPowerSourceDesc =
    233        ::IOPSGetPowerSourceDescription(data, source);
    234    if (!currPowerSourceDesc) {
    235      continue;
    236    }
    237 
    238    // Get a battery level estimate. This key is required but does not always
    239    // exist.
    240    int currentCapacity = 0;
    241    const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc,
    242                                               CFSTR(kIOPSCurrentCapacityKey));
    243    if (!cfRef) {
    244      continue;
    245    }
    246    ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type,
    247                       &currentCapacity);
    248 
    249    // This key is also required.
    250    int maxCapacity = 0;
    251    cfRef =
    252        ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey));
    253    ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity);
    254 
    255    if (maxCapacity > 0) {
    256      level = static_cast<double>(currentCapacity) /
    257              static_cast<double>(maxCapacity);
    258    }
    259 
    260    // Find out if we're charging.
    261    // This key is optional, we fallback to kDefaultCharging if the current
    262    // power source doesn't have that info.
    263    if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc,
    264                                        CFSTR(kIOPSIsChargingKey), &cfRef)) {
    265      charging = ::CFBooleanGetValue((CFBooleanRef)cfRef);
    266 
    267      // Get an estimate of how long it's going to take until we're fully
    268      // charged. This key is optional.
    269      if (charging) {
    270        // Default value that will be changed if we happen to find the actual
    271        // remaining time.
    272        remainingTime =
    273            level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime;
    274 
    275        if (::CFDictionaryGetValueIfPresent(
    276                currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) {
    277          int timeToCharge;
    278          ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType,
    279                             &timeToCharge);
    280          if (timeToCharge != kIOPSTimeRemainingUnknown) {
    281            remainingTime = timeToCharge * 60;
    282          }
    283        }
    284      } else if (sIOPSGetTimeRemainingEstimate) {  // not charging
    285        // See if we can get a time estimate.
    286        CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate();
    287        if (estimate == kIOPSTimeRemainingUnlimited ||
    288            estimate == kIOPSTimeRemainingUnknown) {
    289          remainingTime = kUnknownRemainingTime;
    290        } else {
    291          remainingTime = estimate;
    292        }
    293      }
    294    }
    295 
    296    break;
    297  }
    298 
    299  bool isNewData = level != power->mLevel || charging != power->mCharging ||
    300                   remainingTime != power->mRemainingTime;
    301 
    302  power->mRemainingTime = remainingTime;
    303  power->mCharging = charging;
    304  power->mLevel = level;
    305 
    306  // Notify the observers if stuff changed.
    307  if (power->mShouldNotify && isNewData) {
    308    hal::NotifyBatteryChange(hal::BatteryInformation(
    309        power->mLevel, power->mCharging, power->mRemainingTime));
    310  }
    311 
    312  ::CFRelease(data);
    313  ::CFRelease(list);
    314 }
    315 
    316 }  // namespace hal_impl
    317 }  // namespace mozilla