Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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