summaryrefslogtreecommitdiffstats
path: root/hal/cocoa/CocoaBattery.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--hal/cocoa/CocoaBattery.cpp317
1 files changed, 317 insertions, 0 deletions
diff --git a/hal/cocoa/CocoaBattery.cpp b/hal/cocoa/CocoaBattery.cpp
new file mode 100644
index 0000000000..a6c4b689e1
--- /dev/null
+++ b/hal/cocoa/CocoaBattery.cpp
@@ -0,0 +1,317 @@
+/* -*- 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,
+ &currentCapacity);
+
+ // 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