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
39 static MacPowerInformationService
* GetInstance();
40 static void Shutdown();
41 static bool IsShuttingDown();
43 void BeginListening();
46 static void HandleChange(void *aContext
);
48 ~MacPowerInformationService();
51 MacPowerInformationService();
53 // The reference to the runloop that is notified of power changes.
54 CFRunLoopSourceRef mRunLoopSource
;
58 double mRemainingTime
;
61 friend void GetCurrentBatteryInformation(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
MacPowerInformationService::sIOPSGetTimeRemainingEstimate
;
74 * Implementation of mozilla::hal_impl::EnableBatteryNotifications,
75 * mozilla::hal_impl::DisableBatteryNotifications,
76 * and mozilla::hal_impl::GetCurrentBatteryInformation.
80 EnableBatteryNotifications()
82 if (!MacPowerInformationService::IsShuttingDown()) {
83 MacPowerInformationService::GetInstance()->BeginListening();
88 DisableBatteryNotifications()
90 if (!MacPowerInformationService::IsShuttingDown()) {
91 MacPowerInformationService::GetInstance()->StopListening();
96 GetCurrentBatteryInformation(hal::BatteryInformation
* aBatteryInfo
)
98 MacPowerInformationService
* powerService
= MacPowerInformationService::GetInstance();
100 aBatteryInfo
->level() = powerService
->mLevel
;
101 aBatteryInfo
->charging() = powerService
->mCharging
;
102 aBatteryInfo
->remainingTime() = powerService
->mRemainingTime
;
105 bool MacPowerInformationService::sShuttingDown
= false;
108 * Following is the implementation of MacPowerInformationService.
111 MacPowerInformationService
* MacPowerInformationService::sInstance
= nullptr;
114 struct SingletonDestroyer MOZ_FINAL
: public nsIObserver
120 ~SingletonDestroyer() {}
123 NS_IMPL_ISUPPORTS(SingletonDestroyer
, nsIObserver
)
126 SingletonDestroyer::Observe(nsISupports
*, const char* aTopic
, const char16_t
*)
128 MOZ_ASSERT(!strcmp(aTopic
, "xpcom-shutdown"));
129 MacPowerInformationService::Shutdown();
132 } // anonymous namespace
134 /* static */ MacPowerInformationService
*
135 MacPowerInformationService::GetInstance()
141 sInstance
= new MacPowerInformationService();
143 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
145 obs
->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
152 MacPowerInformationService::IsShuttingDown()
154 return sShuttingDown
;
158 MacPowerInformationService::Shutdown()
160 sShuttingDown
= true;
165 MacPowerInformationService::MacPowerInformationService()
166 : mRunLoopSource(nullptr)
167 , mLevel(kDefaultLevel
)
168 , mCharging(kDefaultCharging
)
169 , mRemainingTime(kDefaultRemainingTime
)
170 , mShouldNotify(false)
172 // IOPSGetTimeRemainingEstimate (and the related constants) are only available
173 // on 10.7, so we test for their presence at runtime.
174 sIOKitFramework
= dlopen(IOKIT_FRAMEWORK_PATH
, RTLD_LAZY
| RTLD_LOCAL
);
175 if (sIOKitFramework
) {
176 sIOPSGetTimeRemainingEstimate
=
177 (IOPSGetTimeRemainingEstimateFunc
)dlsym(sIOKitFramework
, "IOPSGetTimeRemainingEstimate");
179 sIOPSGetTimeRemainingEstimate
= nullptr;
183 MacPowerInformationService::~MacPowerInformationService()
185 MOZ_ASSERT(!mRunLoopSource
,
186 "The observers have not been correctly removed! "
187 "(StopListening should have been called)");
189 if (sIOKitFramework
) {
190 dlclose(sIOKitFramework
);
195 MacPowerInformationService::BeginListening()
197 // Set ourselves up to be notified about changes.
198 MOZ_ASSERT(!mRunLoopSource
, "IOPS Notification Loop Source already set up. "
199 "(StopListening should have been called)");
201 mRunLoopSource
= ::IOPSNotificationCreateRunLoopSource(HandleChange
, this);
202 if (mRunLoopSource
) {
203 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource
,
204 kCFRunLoopDefaultMode
);
206 // Invoke our callback now so we have data if GetCurrentBatteryInformation is
207 // called before a change happens.
209 mShouldNotify
= true;
214 MacPowerInformationService::StopListening()
216 MOZ_ASSERT(mRunLoopSource
, "IOPS Notification Loop Source not set up. "
217 "(StopListening without BeginListening)");
219 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource
,
220 kCFRunLoopDefaultMode
);
221 mRunLoopSource
= nullptr;
225 MacPowerInformationService::HandleChange(void* aContext
) {
226 MacPowerInformationService
* power
=
227 static_cast<MacPowerInformationService
*>(aContext
);
229 CFTypeRef data
= ::IOPSCopyPowerSourcesInfo();
235 // Get the list of power sources.
236 CFArrayRef list
= ::IOPSCopyPowerSourcesList(data
);
242 // Default values. These will be used if there are 0 sources or we can't find
243 // better information.
244 double level
= kDefaultLevel
;
245 double charging
= kDefaultCharging
;
246 double remainingTime
= kDefaultRemainingTime
;
248 // Look for the first battery power source to give us the information we need.
249 // Usually there's only 1 available, depending on current power source.
250 for (CFIndex i
= 0; i
< ::CFArrayGetCount(list
); ++i
) {
251 CFTypeRef source
= ::CFArrayGetValueAtIndex(list
, i
);
252 CFDictionaryRef currPowerSourceDesc
= ::IOPSGetPowerSourceDescription(data
, source
);
253 if (!currPowerSourceDesc
) {
257 // Get a battery level estimate. This key is required.
258 int currentCapacity
= 0;
259 const void* cfRef
= ::CFDictionaryGetValue(currPowerSourceDesc
, CFSTR(kIOPSCurrentCapacityKey
));
260 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
, ¤tCapacity
);
262 // This key is also required.
264 cfRef
= ::CFDictionaryGetValue(currPowerSourceDesc
, CFSTR(kIOPSMaxCapacityKey
));
265 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberSInt32Type
, &maxCapacity
);
267 if (maxCapacity
> 0) {
268 level
= static_cast<double>(currentCapacity
)/static_cast<double>(maxCapacity
);
271 // Find out if we're charging.
272 // This key is optional, we fallback to kDefaultCharging if the current power
273 // source doesn't have that info.
274 if(::CFDictionaryGetValueIfPresent(currPowerSourceDesc
, CFSTR(kIOPSIsChargingKey
), &cfRef
)) {
275 charging
= ::CFBooleanGetValue((CFBooleanRef
)cfRef
);
277 // Get an estimate of how long it's going to take until we're fully charged.
278 // This key is optional.
280 // Default value that will be changed if we happen to find the actual
282 remainingTime
= level
== 1.0 ? kDefaultRemainingTime
: kUnknownRemainingTime
;
284 if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc
,
285 CFSTR(kIOPSTimeToFullChargeKey
), &cfRef
)) {
287 ::CFNumberGetValue((CFNumberRef
)cfRef
, kCFNumberIntType
, &timeToCharge
);
288 if (timeToCharge
!= kIOPSTimeRemainingUnknown
) {
289 remainingTime
= timeToCharge
*60;
292 } else if (sIOPSGetTimeRemainingEstimate
) { // not charging
293 // See if we can get a time estimate.
294 CFTimeInterval estimate
= sIOPSGetTimeRemainingEstimate();
295 if (estimate
== kIOPSTimeRemainingUnlimited
|| estimate
== kIOPSTimeRemainingUnknown
) {
296 remainingTime
= kUnknownRemainingTime
;
298 remainingTime
= estimate
;
306 bool isNewData
= level
!= power
->mLevel
|| charging
!= power
->mCharging
||
307 remainingTime
!= power
->mRemainingTime
;
309 power
->mRemainingTime
= remainingTime
;
310 power
->mCharging
= charging
;
311 power
->mLevel
= level
;
313 // Notify the observers if stuff changed.
314 if (power
->mShouldNotify
&& isNewData
) {
315 hal::NotifyBatteryChange(hal::BatteryInformation(power
->mLevel
,
317 power
->mRemainingTime
));
324 } // namespace hal_impl
325 } // namespace mozilla