1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #import <CoreFoundation/CoreFoundation.h>
8 #import <IOKit/ps/IOPowerSources.h>
9 #import <IOKit/ps/IOPSKeys.h>
11 #include <mozilla/Hal.h>
12 #include <mozilla/dom/battery/Constants.h>
13 #include <mozilla/Services.h>
15 #include <nsIObserverService.h>
16 #include <nsIObserver.h>
20 #define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit"
22 #ifndef kIOPSTimeRemainingUnknown
23 # define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0)
25 #ifndef kIOPSTimeRemainingUnlimited
26 # define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0)
29 using namespace mozilla::dom::battery
;
34 typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc
)(void);
36 class MacPowerInformationService
{
38 static MacPowerInformationService
* GetInstance();
39 static void Shutdown();
40 static bool IsShuttingDown();
42 void BeginListening();
45 static void HandleChange(void* aContext
);
47 ~MacPowerInformationService();
50 MacPowerInformationService();
52 // The reference to the runloop that is notified of power changes.
53 CFRunLoopSourceRef mRunLoopSource
;
57 double mRemainingTime
;
60 friend void GetCurrentBatteryInformation(
61 hal::BatteryInformation
* aBatteryInfo
);
63 static MacPowerInformationService
* sInstance
;
64 static bool sShuttingDown
;
66 static void* sIOKitFramework
;
67 static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate
;
70 void* MacPowerInformationService::sIOKitFramework
;
71 IOPSGetTimeRemainingEstimateFunc
72 MacPowerInformationService::sIOPSGetTimeRemainingEstimate
;
75 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
76 * mozilla::hal_impl::DisableBatteryNotifications,
77 * and mozilla::hal_impl::GetCurrentBatteryInformation.
80 void EnableBatteryNotifications() {
81 if (!MacPowerInformationService::IsShuttingDown()) {
82 MacPowerInformationService::GetInstance()->BeginListening();
86 void DisableBatteryNotifications() {
87 if (!MacPowerInformationService::IsShuttingDown()) {
88 MacPowerInformationService::GetInstance()->StopListening();
92 void GetCurrentBatteryInformation(hal::BatteryInformation
* aBatteryInfo
) {
93 MacPowerInformationService
* powerService
=
94 MacPowerInformationService::GetInstance();
96 aBatteryInfo
->level() = powerService
->mLevel
;
97 aBatteryInfo
->charging() = powerService
->mCharging
;
98 aBatteryInfo
->remainingTime() = powerService
->mRemainingTime
;
101 bool MacPowerInformationService::sShuttingDown
= false;
104 * Following is the implementation of MacPowerInformationService.
107 MacPowerInformationService
* MacPowerInformationService::sInstance
= nullptr;
110 struct SingletonDestroyer final
: public nsIObserver
{
115 ~SingletonDestroyer() {}
118 NS_IMPL_ISUPPORTS(SingletonDestroyer
, nsIObserver
)
121 SingletonDestroyer::Observe(nsISupports
*, const char* aTopic
, const char16_t
*) {
122 MOZ_ASSERT(!strcmp(aTopic
, "xpcom-shutdown"));
123 MacPowerInformationService::Shutdown();
129 MacPowerInformationService
* MacPowerInformationService::GetInstance() {
134 sInstance
= new MacPowerInformationService();
136 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
138 obs
->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
144 bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown
; }
146 void MacPowerInformationService::Shutdown() {
147 sShuttingDown
= true;
152 MacPowerInformationService::MacPowerInformationService()
153 : mRunLoopSource(nullptr),
154 mLevel(kDefaultLevel
),
155 mCharging(kDefaultCharging
),
156 mRemainingTime(kDefaultRemainingTime
),
157 mShouldNotify(false) {
158 // IOPSGetTimeRemainingEstimate (and the related constants) are only available
159 // on 10.7, so we test for their presence at runtime.
160 sIOKitFramework
= dlopen(IOKIT_FRAMEWORK_PATH
, RTLD_LAZY
| RTLD_LOCAL
);
161 if (sIOKitFramework
) {
162 sIOPSGetTimeRemainingEstimate
= (IOPSGetTimeRemainingEstimateFunc
)dlsym(
163 sIOKitFramework
, "IOPSGetTimeRemainingEstimate");
165 sIOPSGetTimeRemainingEstimate
= nullptr;
169 MacPowerInformationService::~MacPowerInformationService() {
170 MOZ_ASSERT(!mRunLoopSource
,
171 "The observers have not been correctly removed! "
172 "(StopListening should have been called)");
174 if (sIOKitFramework
) {
175 dlclose(sIOKitFramework
);
179 void MacPowerInformationService::BeginListening() {
180 // Set ourselves up to be notified about changes.
181 MOZ_ASSERT(!mRunLoopSource
,
182 "IOPS Notification Loop Source already set up. "
183 "(StopListening should have been called)");
185 mRunLoopSource
= ::IOPSNotificationCreateRunLoopSource(HandleChange
, this);
186 if (mRunLoopSource
) {
187 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource
,
188 kCFRunLoopDefaultMode
);
190 // Invoke our callback now so we have data if GetCurrentBatteryInformation
191 // is called before a change happens.
193 mShouldNotify
= true;
197 void MacPowerInformationService::StopListening() {
198 MOZ_ASSERT(mRunLoopSource
,
199 "IOPS Notification Loop Source not set up. "
200 "(StopListening without BeginListening)");
202 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource
,
203 kCFRunLoopDefaultMode
);
204 mRunLoopSource
= nullptr;
207 void MacPowerInformationService::HandleChange(void* aContext
) {
208 MacPowerInformationService
* power
=
209 static_cast<MacPowerInformationService
*>(aContext
);
211 CFTypeRef data
= ::IOPSCopyPowerSourcesInfo();
217 // Get the list of power sources.
218 CFArrayRef list
= ::IOPSCopyPowerSourcesList(data
);
224 // Default values. These will be used if there are 0 sources or we can't find
225 // better information.
226 double level
= kDefaultLevel
;
227 double charging
= kDefaultCharging
;
228 double remainingTime
= kDefaultRemainingTime
;
230 // Look for the first battery power source to give us the information we need.
231 // Usually there's only 1 available, depending on current power source.
232 for (CFIndex i
= 0; i
< ::CFArrayGetCount(list
); ++i
) {
233 CFTypeRef source
= ::CFArrayGetValueAtIndex(list
, i
);
234 CFDictionaryRef currPowerSourceDesc
=
235 ::IOPSGetPowerSourceDescription(data
, source
);
236 if (!currPowerSourceDesc
) {
240 // Get a battery level estimate. This key is required but does not always
242 int currentCapacity
= 0;
243 const void* cfRef
= ::CFDictionaryGetValue(currPowerSourceDesc
,
244 CFSTR(kIOPSCurrentCapacityKey
));
248 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
,
251 // This key is also required.
254 ::CFDictionaryGetValue(currPowerSourceDesc
, CFSTR(kIOPSMaxCapacityKey
));
255 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
, &maxCapacity
);
257 if (maxCapacity
> 0) {
258 level
= static_cast<double>(currentCapacity
) /
259 static_cast<double>(maxCapacity
);
262 // Find out if we're charging.
263 // This key is optional, we fallback to kDefaultCharging if the current
264 // power source doesn't have that info.
265 if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc
,
266 CFSTR(kIOPSIsChargingKey
), &cfRef
)) {
267 charging
= ::CFBooleanGetValue((CFBooleanRef
)cfRef
);
269 // Get an estimate of how long it's going to take until we're fully
270 // charged. This key is optional.
272 // Default value that will be changed if we happen to find the actual
275 level
== 1.0 ? kDefaultRemainingTime
: kUnknownRemainingTime
;
277 if (::CFDictionaryGetValueIfPresent(
278 currPowerSourceDesc
, CFSTR(kIOPSTimeToFullChargeKey
), &cfRef
)) {
280 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberIntType
,
282 if (timeToCharge
!= kIOPSTimeRemainingUnknown
) {
283 remainingTime
= timeToCharge
* 60;
286 } else if (sIOPSGetTimeRemainingEstimate
) { // not charging
287 // See if we can get a time estimate.
288 CFTimeInterval estimate
= sIOPSGetTimeRemainingEstimate();
289 if (estimate
== kIOPSTimeRemainingUnlimited
||
290 estimate
== kIOPSTimeRemainingUnknown
) {
291 remainingTime
= kUnknownRemainingTime
;
293 remainingTime
= estimate
;
301 bool isNewData
= level
!= power
->mLevel
|| charging
!= power
->mCharging
||
302 remainingTime
!= power
->mRemainingTime
;
304 power
->mRemainingTime
= remainingTime
;
305 power
->mCharging
= charging
;
306 power
->mLevel
= level
;
308 // Notify the observers if stuff changed.
309 if (power
->mShouldNotify
&& isNewData
) {
310 hal::NotifyBatteryChange(hal::BatteryInformation(
311 power
->mLevel
, power
->mCharging
, power
->mRemainingTime
));
318 } // namespace hal_impl
319 } // namespace mozilla