diff options
Diffstat (limited to '')
-rw-r--r-- | hal/cocoa/CocoaBattery.cpp | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/hal/cocoa/CocoaBattery.cpp b/hal/cocoa/CocoaBattery.cpp new file mode 100644 index 0000000000..60ccaa89bd --- /dev/null +++ b/hal/cocoa/CocoaBattery.cpp @@ -0,0 +1,319 @@ +/* -*- 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() { + MOZ_ASSERT(mRunLoopSource, + "IOPS Notification Loop Source not set up. " + "(StopListening without BeginListening)"); + + ::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 |