hal/cocoa/CocoaBattery.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/hal/cocoa/CocoaBattery.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,307 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#import <CoreFoundation/CoreFoundation.h>
    1.11 +#import <IOKit/ps/IOPowerSources.h>
    1.12 +#import <IOKit/ps/IOPSKeys.h>
    1.13 +
    1.14 +#include <mozilla/Hal.h>
    1.15 +#include <mozilla/dom/battery/Constants.h>
    1.16 +#include <mozilla/Services.h>
    1.17 +
    1.18 +#include <nsIObserverService.h>
    1.19 +#include <nsIObserver.h>
    1.20 +
    1.21 +#include <dlfcn.h>
    1.22 +
    1.23 +#define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit"
    1.24 +
    1.25 +#ifndef kIOPSTimeRemainingUnknown
    1.26 +  #define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0)
    1.27 +#endif
    1.28 +#ifndef kIOPSTimeRemainingUnlimited
    1.29 +  #define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0)
    1.30 +#endif
    1.31 +
    1.32 +using namespace mozilla::dom::battery;
    1.33 +
    1.34 +namespace mozilla {
    1.35 +namespace hal_impl {
    1.36 +
    1.37 +typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void);
    1.38 +
    1.39 +class MacPowerInformationService
    1.40 +{
    1.41 +public:
    1.42 +  static MacPowerInformationService* GetInstance();
    1.43 +  static void Shutdown();
    1.44 +
    1.45 +  void BeginListening();
    1.46 +  void StopListening();
    1.47 +
    1.48 +  static void HandleChange(void *aContext);
    1.49 +
    1.50 +  ~MacPowerInformationService();
    1.51 +
    1.52 +private:
    1.53 +  MacPowerInformationService();
    1.54 +
    1.55 +  // The reference to the runloop that is notified of power changes.
    1.56 +  CFRunLoopSourceRef mRunLoopSource;
    1.57 +
    1.58 +  double mLevel;
    1.59 +  bool mCharging;
    1.60 +  double mRemainingTime;
    1.61 +  bool mShouldNotify;
    1.62 +
    1.63 +  friend void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo);
    1.64 +
    1.65 +  static MacPowerInformationService* sInstance;
    1.66 +
    1.67 +  static void* sIOKitFramework;
    1.68 +  static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate;
    1.69 +};
    1.70 +
    1.71 +void* MacPowerInformationService::sIOKitFramework;
    1.72 +IOPSGetTimeRemainingEstimateFunc MacPowerInformationService::sIOPSGetTimeRemainingEstimate;
    1.73 +
    1.74 +/*
    1.75 + * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
    1.76 + *                   mozilla::hal_impl::DisableBatteryNotifications,
    1.77 + *               and mozilla::hal_impl::GetCurrentBatteryInformation.
    1.78 + */
    1.79 +
    1.80 +void
    1.81 +EnableBatteryNotifications()
    1.82 +{
    1.83 +  MacPowerInformationService::GetInstance()->BeginListening();
    1.84 +}
    1.85 +
    1.86 +void
    1.87 +DisableBatteryNotifications()
    1.88 +{
    1.89 +  MacPowerInformationService::GetInstance()->StopListening();
    1.90 +}
    1.91 +
    1.92 +void
    1.93 +GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo)
    1.94 +{
    1.95 +  MacPowerInformationService* powerService = MacPowerInformationService::GetInstance();
    1.96 +
    1.97 +  aBatteryInfo->level() = powerService->mLevel;
    1.98 +  aBatteryInfo->charging() = powerService->mCharging;
    1.99 +  aBatteryInfo->remainingTime() = powerService->mRemainingTime;
   1.100 +}
   1.101 +
   1.102 +/*
   1.103 + * Following is the implementation of MacPowerInformationService.
   1.104 + */
   1.105 +
   1.106 +MacPowerInformationService* MacPowerInformationService::sInstance = nullptr;
   1.107 +
   1.108 +namespace {
   1.109 +struct SingletonDestroyer MOZ_FINAL : public nsIObserver
   1.110 +{
   1.111 +  NS_DECL_ISUPPORTS
   1.112 +  NS_DECL_NSIOBSERVER
   1.113 +};
   1.114 +
   1.115 +NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver)
   1.116 +
   1.117 +NS_IMETHODIMP
   1.118 +SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*)
   1.119 +{
   1.120 +  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
   1.121 +  MacPowerInformationService::Shutdown();
   1.122 +  return NS_OK;
   1.123 +}
   1.124 +} // anonymous namespace
   1.125 +
   1.126 +/* static */ MacPowerInformationService*
   1.127 +MacPowerInformationService::GetInstance()
   1.128 +{
   1.129 +  if (sInstance) {
   1.130 +    return sInstance;
   1.131 +  }
   1.132 +
   1.133 +  sInstance = new MacPowerInformationService();
   1.134 +
   1.135 +  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   1.136 +  if (obs) {
   1.137 +    obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
   1.138 +  }
   1.139 +
   1.140 +  return sInstance;
   1.141 +}
   1.142 +
   1.143 +void
   1.144 +MacPowerInformationService::Shutdown()
   1.145 +{
   1.146 +  delete sInstance;
   1.147 +  sInstance = nullptr;
   1.148 +}
   1.149 +
   1.150 +MacPowerInformationService::MacPowerInformationService()
   1.151 +  : mRunLoopSource(nullptr)
   1.152 +  , mLevel(kDefaultLevel)
   1.153 +  , mCharging(kDefaultCharging)
   1.154 +  , mRemainingTime(kDefaultRemainingTime)
   1.155 +  , mShouldNotify(false)
   1.156 +{
   1.157 +  // IOPSGetTimeRemainingEstimate (and the related constants) are only available
   1.158 +  // on 10.7, so we test for their presence at runtime.
   1.159 +  sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL);
   1.160 +  if (sIOKitFramework) {
   1.161 +    sIOPSGetTimeRemainingEstimate =
   1.162 +      (IOPSGetTimeRemainingEstimateFunc)dlsym(sIOKitFramework, "IOPSGetTimeRemainingEstimate");
   1.163 +  } else {
   1.164 +    sIOPSGetTimeRemainingEstimate = nullptr;
   1.165 +  }
   1.166 +}
   1.167 +
   1.168 +MacPowerInformationService::~MacPowerInformationService()
   1.169 +{
   1.170 +  MOZ_ASSERT(!mRunLoopSource,
   1.171 +               "The observers have not been correctly removed! "
   1.172 +               "(StopListening should have been called)");
   1.173 +
   1.174 +  if (sIOKitFramework) {
   1.175 +    dlclose(sIOKitFramework);
   1.176 +  }
   1.177 +}
   1.178 +
   1.179 +void
   1.180 +MacPowerInformationService::BeginListening()
   1.181 +{
   1.182 +  // Set ourselves up to be notified about changes.
   1.183 +  MOZ_ASSERT(!mRunLoopSource, "IOPS Notification Loop Source already set up. "
   1.184 +                              "(StopListening should have been called)");
   1.185 +
   1.186 +  mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this);
   1.187 +  if (mRunLoopSource) {
   1.188 +    ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource,
   1.189 +                         kCFRunLoopDefaultMode);
   1.190 +
   1.191 +    // Invoke our callback now so we have data if GetCurrentBatteryInformation is
   1.192 +    // called before a change happens.
   1.193 +    HandleChange(this);
   1.194 +    mShouldNotify = true;
   1.195 +  }
   1.196 +}
   1.197 +
   1.198 +void
   1.199 +MacPowerInformationService::StopListening()
   1.200 +{
   1.201 +  MOZ_ASSERT(mRunLoopSource, "IOPS Notification Loop Source not set up. "
   1.202 +                             "(StopListening without BeginListening)");
   1.203 +
   1.204 +  ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource,
   1.205 +                          kCFRunLoopDefaultMode);
   1.206 +  mRunLoopSource = nullptr;
   1.207 +}
   1.208 +
   1.209 +void
   1.210 +MacPowerInformationService::HandleChange(void* aContext) {
   1.211 +  MacPowerInformationService* power =
   1.212 +    static_cast<MacPowerInformationService*>(aContext);
   1.213 +
   1.214 +  CFTypeRef data = ::IOPSCopyPowerSourcesInfo();
   1.215 +  if (!data) {
   1.216 +    ::CFRelease(data);
   1.217 +    return;
   1.218 +  }
   1.219 +
   1.220 +  // Get the list of power sources.
   1.221 +  CFArrayRef list = ::IOPSCopyPowerSourcesList(data);
   1.222 +  if (!list) {
   1.223 +    ::CFRelease(list);
   1.224 +    return;
   1.225 +  }
   1.226 +
   1.227 +  // Default values. These will be used if there are 0 sources or we can't find
   1.228 +  // better information.
   1.229 +  double level = kDefaultLevel;
   1.230 +  double charging = kDefaultCharging;
   1.231 +  double remainingTime = kDefaultRemainingTime;
   1.232 +
   1.233 +  // Look for the first battery power source to give us the information we need.
   1.234 +  // Usually there's only 1 available, depending on current power source.
   1.235 +  for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) {
   1.236 +    CFTypeRef source = ::CFArrayGetValueAtIndex(list, i);
   1.237 +    CFDictionaryRef currPowerSourceDesc = ::IOPSGetPowerSourceDescription(data, source);
   1.238 +    if (!currPowerSourceDesc) {
   1.239 +      continue;
   1.240 +    }
   1.241 +
   1.242 +    // Get a battery level estimate. This key is required.
   1.243 +    int currentCapacity = 0;
   1.244 +    const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSCurrentCapacityKey));
   1.245 +    ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &currentCapacity);
   1.246 +
   1.247 +    // This key is also required.
   1.248 +    int maxCapacity = 0;
   1.249 +    cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey));
   1.250 +    ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity);
   1.251 +
   1.252 +    if (maxCapacity > 0) {
   1.253 +      level = static_cast<double>(currentCapacity)/static_cast<double>(maxCapacity);
   1.254 +    }
   1.255 +
   1.256 +    // Find out if we're charging.
   1.257 +    // This key is optional, we fallback to kDefaultCharging if the current power
   1.258 +    // source doesn't have that info.
   1.259 +    if(::CFDictionaryGetValueIfPresent(currPowerSourceDesc, CFSTR(kIOPSIsChargingKey), &cfRef)) {
   1.260 +      charging = ::CFBooleanGetValue((CFBooleanRef)cfRef);
   1.261 +
   1.262 +      // Get an estimate of how long it's going to take until we're fully charged.
   1.263 +      // This key is optional.
   1.264 +      if (charging) {
   1.265 +        // Default value that will be changed if we happen to find the actual
   1.266 +        // remaining time.
   1.267 +        remainingTime = level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime;
   1.268 +
   1.269 +        if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc,
   1.270 +                CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) {
   1.271 +          int timeToCharge;
   1.272 +          ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &timeToCharge);
   1.273 +          if (timeToCharge != kIOPSTimeRemainingUnknown) {
   1.274 +            remainingTime = timeToCharge*60;
   1.275 +          }
   1.276 +        }
   1.277 +      } else if (sIOPSGetTimeRemainingEstimate) { // not charging
   1.278 +        // See if we can get a time estimate.
   1.279 +        CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate();
   1.280 +        if (estimate == kIOPSTimeRemainingUnlimited || estimate == kIOPSTimeRemainingUnknown) {
   1.281 +          remainingTime = kUnknownRemainingTime;
   1.282 +        } else {
   1.283 +          remainingTime = estimate;
   1.284 +        }
   1.285 +      }
   1.286 +    }
   1.287 +
   1.288 +    break;
   1.289 +  }
   1.290 +
   1.291 +  bool isNewData = level != power->mLevel || charging != power->mCharging ||
   1.292 +                   remainingTime != power->mRemainingTime;
   1.293 +
   1.294 +  power->mRemainingTime = remainingTime;
   1.295 +  power->mCharging = charging;
   1.296 +  power->mLevel = level;
   1.297 +
   1.298 +  // Notify the observers if stuff changed.
   1.299 +  if (power->mShouldNotify && isNewData) {
   1.300 +    hal::NotifyBatteryChange(hal::BatteryInformation(power->mLevel,
   1.301 +                                                     power->mCharging,
   1.302 +                                                     power->mRemainingTime));
   1.303 +  }
   1.304 +
   1.305 +  ::CFRelease(data);
   1.306 +  ::CFRelease(list);
   1.307 +}
   1.308 +
   1.309 +} // namespace hal_impl
   1.310 +} // namespace mozilla

mercurial