зеркало из https://github.com/mozilla/gecko-dev.git
318 строки
9.6 KiB
C++
318 строки
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
|