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