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 if (mRunLoopSource
) {
199 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource
,
200 kCFRunLoopDefaultMode
);
201 mRunLoopSource
= nullptr;
205 void MacPowerInformationService::HandleChange(void* aContext
) {
206 MacPowerInformationService
* power
=
207 static_cast<MacPowerInformationService
*>(aContext
);
209 CFTypeRef data
= ::IOPSCopyPowerSourcesInfo();
215 // Get the list of power sources.
216 CFArrayRef list
= ::IOPSCopyPowerSourcesList(data
);
222 // Default values. These will be used if there are 0 sources or we can't find
223 // better information.
224 double level
= kDefaultLevel
;
225 double charging
= kDefaultCharging
;
226 double remainingTime
= kDefaultRemainingTime
;
228 // Look for the first battery power source to give us the information we need.
229 // Usually there's only 1 available, depending on current power source.
230 for (CFIndex i
= 0; i
< ::CFArrayGetCount(list
); ++i
) {
231 CFTypeRef source
= ::CFArrayGetValueAtIndex(list
, i
);
232 CFDictionaryRef currPowerSourceDesc
=
233 ::IOPSGetPowerSourceDescription(data
, source
);
234 if (!currPowerSourceDesc
) {
238 // Get a battery level estimate. This key is required but does not always
240 int currentCapacity
= 0;
241 const void* cfRef
= ::CFDictionaryGetValue(currPowerSourceDesc
,
242 CFSTR(kIOPSCurrentCapacityKey
));
246 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
,
249 // This key is also required.
252 ::CFDictionaryGetValue(currPowerSourceDesc
, CFSTR(kIOPSMaxCapacityKey
));
253 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
, &maxCapacity
);
255 if (maxCapacity
> 0) {
256 level
= static_cast<double>(currentCapacity
) /
257 static_cast<double>(maxCapacity
);
260 // Find out if we're charging.
261 // This key is optional, we fallback to kDefaultCharging if the current
262 // power source doesn't have that info.
263 if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc
,
264 CFSTR(kIOPSIsChargingKey
), &cfRef
)) {
265 charging
= ::CFBooleanGetValue((CFBooleanRef
)cfRef
);
267 // Get an estimate of how long it's going to take until we're fully
268 // charged. This key is optional.
270 // Default value that will be changed if we happen to find the actual
273 level
== 1.0 ? kDefaultRemainingTime
: kUnknownRemainingTime
;
275 if (::CFDictionaryGetValueIfPresent(
276 currPowerSourceDesc
, CFSTR(kIOPSTimeToFullChargeKey
), &cfRef
)) {
278 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberIntType
,
280 if (timeToCharge
!= kIOPSTimeRemainingUnknown
) {
281 remainingTime
= timeToCharge
* 60;
284 } else if (sIOPSGetTimeRemainingEstimate
) { // not charging
285 // See if we can get a time estimate.
286 CFTimeInterval estimate
= sIOPSGetTimeRemainingEstimate();
287 if (estimate
== kIOPSTimeRemainingUnlimited
||
288 estimate
== kIOPSTimeRemainingUnknown
) {
289 remainingTime
= kUnknownRemainingTime
;
291 remainingTime
= estimate
;
299 bool isNewData
= level
!= power
->mLevel
|| charging
!= power
->mCharging
||
300 remainingTime
!= power
->mRemainingTime
;
302 power
->mRemainingTime
= remainingTime
;
303 power
->mCharging
= charging
;
304 power
->mLevel
= level
;
306 // Notify the observers if stuff changed.
307 if (power
->mShouldNotify
&& isNewData
) {
308 hal::NotifyBatteryChange(hal::BatteryInformation(
309 power
->mLevel
, power
->mCharging
, power
->mRemainingTime
));
316 } // namespace hal_impl
317 } // namespace mozilla