Bug 1686820 [wpt PR 27193] - Origin-keyed agent clusters: make COI imply origin-keyin...
[gecko.git] / hal / cocoa / CocoaBattery.cpp
blob60ccaa89bdea44847906fa9ef54a59b430bf398f
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>
18 #include <dlfcn.h>
20 #define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit"
22 #ifndef kIOPSTimeRemainingUnknown
23 # define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0)
24 #endif
25 #ifndef kIOPSTimeRemainingUnlimited
26 # define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0)
27 #endif
29 using namespace mozilla::dom::battery;
31 namespace mozilla {
32 namespace hal_impl {
34 typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void);
36 class MacPowerInformationService {
37 public:
38 static MacPowerInformationService* GetInstance();
39 static void Shutdown();
40 static bool IsShuttingDown();
42 void BeginListening();
43 void StopListening();
45 static void HandleChange(void* aContext);
47 ~MacPowerInformationService();
49 private:
50 MacPowerInformationService();
52 // The reference to the runloop that is notified of power changes.
53 CFRunLoopSourceRef mRunLoopSource;
55 double mLevel;
56 bool mCharging;
57 double mRemainingTime;
58 bool mShouldNotify;
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;
109 namespace {
110 struct SingletonDestroyer final : public nsIObserver {
111 NS_DECL_ISUPPORTS
112 NS_DECL_NSIOBSERVER
114 private:
115 ~SingletonDestroyer() {}
118 NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver)
120 NS_IMETHODIMP
121 SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) {
122 MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
123 MacPowerInformationService::Shutdown();
124 return NS_OK;
126 } // namespace
128 /* static */
129 MacPowerInformationService* MacPowerInformationService::GetInstance() {
130 if (sInstance) {
131 return sInstance;
134 sInstance = new MacPowerInformationService();
136 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
137 if (obs) {
138 obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false);
141 return sInstance;
144 bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; }
146 void MacPowerInformationService::Shutdown() {
147 sShuttingDown = true;
148 delete sInstance;
149 sInstance = nullptr;
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");
164 } else {
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.
192 HandleChange(this);
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();
212 if (!data) {
213 ::CFRelease(data);
214 return;
217 // Get the list of power sources.
218 CFArrayRef list = ::IOPSCopyPowerSourcesList(data);
219 if (!list) {
220 ::CFRelease(list);
221 return;
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) {
237 continue;
240 // Get a battery level estimate. This key is required but does not always
241 // exist.
242 int currentCapacity = 0;
243 const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc,
244 CFSTR(kIOPSCurrentCapacityKey));
245 if (!cfRef) {
246 continue;
248 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type,
249 &currentCapacity);
251 // This key is also required.
252 int maxCapacity = 0;
253 cfRef =
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.
271 if (charging) {
272 // Default value that will be changed if we happen to find the actual
273 // remaining time.
274 remainingTime =
275 level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime;
277 if (::CFDictionaryGetValueIfPresent(
278 currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) {
279 int timeToCharge;
280 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType,
281 &timeToCharge);
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;
292 } else {
293 remainingTime = estimate;
298 break;
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));
314 ::CFRelease(data);
315 ::CFRelease(list);
318 } // namespace hal_impl
319 } // namespace mozilla