hal/cocoa/CocoaBattery.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial