forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			317 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #import <CoreFoundation/CoreFoundation.h>
 | |
| #import <IOKit/ps/IOPowerSources.h>
 | |
| #import <IOKit/ps/IOPSKeys.h>
 | |
| 
 | |
| #include <mozilla/Hal.h>
 | |
| #include <mozilla/dom/battery/Constants.h>
 | |
| #include <mozilla/Services.h>
 | |
| 
 | |
| #include <nsIObserverService.h>
 | |
| #include <nsIObserver.h>
 | |
| 
 | |
| #include <dlfcn.h>
 | |
| 
 | |
| #define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit"
 | |
| 
 | |
| #ifndef kIOPSTimeRemainingUnknown
 | |
| #  define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0)
 | |
| #endif
 | |
| #ifndef kIOPSTimeRemainingUnlimited
 | |
| #  define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0)
 | |
| #endif
 | |
| 
 | |
| using namespace mozilla::dom::battery;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace hal_impl {
 | |
| 
 | |
| typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void);
 | |
| 
 | |
| class MacPowerInformationService {
 | |
|  public:
 | |
|   static MacPowerInformationService* GetInstance();
 | |
|   static void Shutdown();
 | |
|   static bool IsShuttingDown();
 | |
| 
 | |
|   void BeginListening();
 | |
|   void StopListening();
 | |
| 
 | |
|   static void HandleChange(void* aContext);
 | |
| 
 | |
|   ~MacPowerInformationService();
 | |
| 
 | |
|  private:
 | |
|   MacPowerInformationService();
 | |
| 
 | |
|   // The reference to the runloop that is notified of power changes.
 | |
|   CFRunLoopSourceRef mRunLoopSource;
 | |
| 
 | |
|   double mLevel;
 | |
|   bool mCharging;
 | |
|   double mRemainingTime;
 | |
|   bool mShouldNotify;
 | |
| 
 | |
|   friend void GetCurrentBatteryInformation(
 | |
|       hal::BatteryInformation* aBatteryInfo);
 | |
| 
 | |
|   static MacPowerInformationService* sInstance;
 | |
|   static bool sShuttingDown;
 | |
| 
 | |
|   static void* sIOKitFramework;
 | |
|   static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate;
 | |
| };
 | |
| 
 | |
| void* MacPowerInformationService::sIOKitFramework;
 | |
| IOPSGetTimeRemainingEstimateFunc
 | |
|     MacPowerInformationService::sIOPSGetTimeRemainingEstimate;
 | |
| 
 | |
| /*
 | |
|  * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
 | |
|  *                   mozilla::hal_impl::DisableBatteryNotifications,
 | |
|  *               and mozilla::hal_impl::GetCurrentBatteryInformation.
 | |
|  */
 | |
| 
 | |
| void EnableBatteryNotifications() {
 | |
|   if (!MacPowerInformationService::IsShuttingDown()) {
 | |
|     MacPowerInformationService::GetInstance()->BeginListening();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DisableBatteryNotifications() {
 | |
|   if (!MacPowerInformationService::IsShuttingDown()) {
 | |
|     MacPowerInformationService::GetInstance()->StopListening();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) {
 | |
|   MacPowerInformationService* powerService =
 | |
|       MacPowerInformationService::GetInstance();
 | |
| 
 | |
|   aBatteryInfo->level() = powerService->mLevel;
 | |
|   aBatteryInfo->charging() = powerService->mCharging;
 | |
|   aBatteryInfo->remainingTime() = powerService->mRemainingTime;
 | |
| }
 | |
| 
 | |
| bool MacPowerInformationService::sShuttingDown = false;
 | |
| 
 | |
| /*
 | |
|  * Following is the implementation of MacPowerInformationService.
 | |
|  */
 | |
| 
 | |
| MacPowerInformationService* MacPowerInformationService::sInstance = nullptr;
 | |
| 
 | |
| namespace {
 | |
| struct SingletonDestroyer final : public nsIObserver {
 | |
|   NS_DECL_ISUPPORTS
 | |
|   NS_DECL_NSIOBSERVER
 | |
| 
 | |
|  private:
 | |
|   ~SingletonDestroyer() {}
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) {
 | |
|   MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
 | |
|   MacPowerInformationService::Shutdown();
 | |
|   return NS_OK;
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| /* static */
 | |
| MacPowerInformationService* MacPowerInformationService::GetInstance() {
 | |
|   if (sInstance) {
 | |
|     return sInstance;
 | |
|   }
 | |
| 
 | |
|   sInstance = new MacPowerInformationService();
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | |
|   if (obs) {
 | |
|     obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
 | |
|   }
 | |
| 
 | |
|   return sInstance;
 | |
| }
 | |
| 
 | |
| bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; }
 | |
| 
 | |
| void MacPowerInformationService::Shutdown() {
 | |
|   sShuttingDown = true;
 | |
|   delete sInstance;
 | |
|   sInstance = nullptr;
 | |
| }
 | |
| 
 | |
| MacPowerInformationService::MacPowerInformationService()
 | |
|     : mRunLoopSource(nullptr),
 | |
|       mLevel(kDefaultLevel),
 | |
|       mCharging(kDefaultCharging),
 | |
|       mRemainingTime(kDefaultRemainingTime),
 | |
|       mShouldNotify(false) {
 | |
|   // IOPSGetTimeRemainingEstimate (and the related constants) are only available
 | |
|   // on 10.7, so we test for their presence at runtime.
 | |
|   sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL);
 | |
|   if (sIOKitFramework) {
 | |
|     sIOPSGetTimeRemainingEstimate = (IOPSGetTimeRemainingEstimateFunc)dlsym(
 | |
|         sIOKitFramework, "IOPSGetTimeRemainingEstimate");
 | |
|   } else {
 | |
|     sIOPSGetTimeRemainingEstimate = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| MacPowerInformationService::~MacPowerInformationService() {
 | |
|   MOZ_ASSERT(!mRunLoopSource,
 | |
|              "The observers have not been correctly removed! "
 | |
|              "(StopListening should have been called)");
 | |
| 
 | |
|   if (sIOKitFramework) {
 | |
|     dlclose(sIOKitFramework);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MacPowerInformationService::BeginListening() {
 | |
|   // Set ourselves up to be notified about changes.
 | |
|   MOZ_ASSERT(!mRunLoopSource,
 | |
|              "IOPS Notification Loop Source already set up. "
 | |
|              "(StopListening should have been called)");
 | |
| 
 | |
|   mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this);
 | |
|   if (mRunLoopSource) {
 | |
|     ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource,
 | |
|                          kCFRunLoopDefaultMode);
 | |
| 
 | |
|     // Invoke our callback now so we have data if GetCurrentBatteryInformation
 | |
|     // is called before a change happens.
 | |
|     HandleChange(this);
 | |
|     mShouldNotify = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MacPowerInformationService::StopListening() {
 | |
|   if (mRunLoopSource) {
 | |
|     ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource,
 | |
|                             kCFRunLoopDefaultMode);
 | |
|     mRunLoopSource = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MacPowerInformationService::HandleChange(void* aContext) {
 | |
|   MacPowerInformationService* power =
 | |
|       static_cast<MacPowerInformationService*>(aContext);
 | |
| 
 | |
|   CFTypeRef data = ::IOPSCopyPowerSourcesInfo();
 | |
|   if (!data) {
 | |
|     ::CFRelease(data);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Get the list of power sources.
 | |
|   CFArrayRef list = ::IOPSCopyPowerSourcesList(data);
 | |
|   if (!list) {
 | |
|     ::CFRelease(list);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Default values. These will be used if there are 0 sources or we can't find
 | |
|   // better information.
 | |
|   double level = kDefaultLevel;
 | |
|   double charging = kDefaultCharging;
 | |
|   double remainingTime = kDefaultRemainingTime;
 | |
| 
 | |
|   // Look for the first battery power source to give us the information we need.
 | |
|   // Usually there's only 1 available, depending on current power source.
 | |
|   for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) {
 | |
|     CFTypeRef source = ::CFArrayGetValueAtIndex(list, i);
 | |
|     CFDictionaryRef currPowerSourceDesc =
 | |
|         ::IOPSGetPowerSourceDescription(data, source);
 | |
|     if (!currPowerSourceDesc) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Get a battery level estimate. This key is required but does not always
 | |
|     // exist.
 | |
|     int currentCapacity = 0;
 | |
|     const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc,
 | |
|                                                CFSTR(kIOPSCurrentCapacityKey));
 | |
|     if (!cfRef) {
 | |
|       continue;
 | |
|     }
 | |
|     ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type,
 | |
|                        ¤tCapacity);
 | |
| 
 | |
|     // This key is also required.
 | |
|     int maxCapacity = 0;
 | |
|     cfRef =
 | |
|         ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey));
 | |
|     ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity);
 | |
| 
 | |
|     if (maxCapacity > 0) {
 | |
|       level = static_cast<double>(currentCapacity) /
 | |
|               static_cast<double>(maxCapacity);
 | |
|     }
 | |
| 
 | |
|     // Find out if we're charging.
 | |
|     // This key is optional, we fallback to kDefaultCharging if the current
 | |
|     // power source doesn't have that info.
 | |
|     if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc,
 | |
|                                         CFSTR(kIOPSIsChargingKey), &cfRef)) {
 | |
|       charging = ::CFBooleanGetValue((CFBooleanRef)cfRef);
 | |
| 
 | |
|       // Get an estimate of how long it's going to take until we're fully
 | |
|       // charged. This key is optional.
 | |
|       if (charging) {
 | |
|         // Default value that will be changed if we happen to find the actual
 | |
|         // remaining time.
 | |
|         remainingTime =
 | |
|             level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime;
 | |
| 
 | |
|         if (::CFDictionaryGetValueIfPresent(
 | |
|                 currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) {
 | |
|           int timeToCharge;
 | |
|           ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType,
 | |
|                              &timeToCharge);
 | |
|           if (timeToCharge != kIOPSTimeRemainingUnknown) {
 | |
|             remainingTime = timeToCharge * 60;
 | |
|           }
 | |
|         }
 | |
|       } else if (sIOPSGetTimeRemainingEstimate) {  // not charging
 | |
|         // See if we can get a time estimate.
 | |
|         CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate();
 | |
|         if (estimate == kIOPSTimeRemainingUnlimited ||
 | |
|             estimate == kIOPSTimeRemainingUnknown) {
 | |
|           remainingTime = kUnknownRemainingTime;
 | |
|         } else {
 | |
|           remainingTime = estimate;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   bool isNewData = level != power->mLevel || charging != power->mCharging ||
 | |
|                    remainingTime != power->mRemainingTime;
 | |
| 
 | |
|   power->mRemainingTime = remainingTime;
 | |
|   power->mCharging = charging;
 | |
|   power->mLevel = level;
 | |
| 
 | |
|   // Notify the observers if stuff changed.
 | |
|   if (power->mShouldNotify && isNewData) {
 | |
|     hal::NotifyBatteryChange(hal::BatteryInformation(
 | |
|         power->mLevel, power->mCharging, power->mRemainingTime));
 | |
|   }
 | |
| 
 | |
|   ::CFRelease(data);
 | |
|   ::CFRelease(list);
 | |
| }
 | |
| 
 | |
| }  // namespace hal_impl
 | |
| }  // namespace mozilla
 | 
