|
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/. */ |
|
6 |
|
7 #import <CoreFoundation/CoreFoundation.h> |
|
8 #import <IOKit/ps/IOPowerSources.h> |
|
9 #import <IOKit/ps/IOPSKeys.h> |
|
10 |
|
11 #include <mozilla/Hal.h> |
|
12 #include <mozilla/dom/battery/Constants.h> |
|
13 #include <mozilla/Services.h> |
|
14 |
|
15 #include <nsIObserverService.h> |
|
16 #include <nsIObserver.h> |
|
17 |
|
18 #include <dlfcn.h> |
|
19 |
|
20 #define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit" |
|
21 |
|
22 #ifndef kIOPSTimeRemainingUnknown |
|
23 #define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0) |
|
24 #endif |
|
25 #ifndef kIOPSTimeRemainingUnlimited |
|
26 #define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0) |
|
27 #endif |
|
28 |
|
29 using namespace mozilla::dom::battery; |
|
30 |
|
31 namespace mozilla { |
|
32 namespace hal_impl { |
|
33 |
|
34 typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void); |
|
35 |
|
36 class MacPowerInformationService |
|
37 { |
|
38 public: |
|
39 static MacPowerInformationService* GetInstance(); |
|
40 static void Shutdown(); |
|
41 |
|
42 void BeginListening(); |
|
43 void StopListening(); |
|
44 |
|
45 static void HandleChange(void *aContext); |
|
46 |
|
47 ~MacPowerInformationService(); |
|
48 |
|
49 private: |
|
50 MacPowerInformationService(); |
|
51 |
|
52 // The reference to the runloop that is notified of power changes. |
|
53 CFRunLoopSourceRef mRunLoopSource; |
|
54 |
|
55 double mLevel; |
|
56 bool mCharging; |
|
57 double mRemainingTime; |
|
58 bool mShouldNotify; |
|
59 |
|
60 friend void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo); |
|
61 |
|
62 static MacPowerInformationService* sInstance; |
|
63 |
|
64 static void* sIOKitFramework; |
|
65 static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate; |
|
66 }; |
|
67 |
|
68 void* MacPowerInformationService::sIOKitFramework; |
|
69 IOPSGetTimeRemainingEstimateFunc MacPowerInformationService::sIOPSGetTimeRemainingEstimate; |
|
70 |
|
71 /* |
|
72 * Implementation of mozilla::hal_impl::EnableBatteryNotifications, |
|
73 * mozilla::hal_impl::DisableBatteryNotifications, |
|
74 * and mozilla::hal_impl::GetCurrentBatteryInformation. |
|
75 */ |
|
76 |
|
77 void |
|
78 EnableBatteryNotifications() |
|
79 { |
|
80 MacPowerInformationService::GetInstance()->BeginListening(); |
|
81 } |
|
82 |
|
83 void |
|
84 DisableBatteryNotifications() |
|
85 { |
|
86 MacPowerInformationService::GetInstance()->StopListening(); |
|
87 } |
|
88 |
|
89 void |
|
90 GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) |
|
91 { |
|
92 MacPowerInformationService* powerService = MacPowerInformationService::GetInstance(); |
|
93 |
|
94 aBatteryInfo->level() = powerService->mLevel; |
|
95 aBatteryInfo->charging() = powerService->mCharging; |
|
96 aBatteryInfo->remainingTime() = powerService->mRemainingTime; |
|
97 } |
|
98 |
|
99 /* |
|
100 * Following is the implementation of MacPowerInformationService. |
|
101 */ |
|
102 |
|
103 MacPowerInformationService* MacPowerInformationService::sInstance = nullptr; |
|
104 |
|
105 namespace { |
|
106 struct SingletonDestroyer MOZ_FINAL : public nsIObserver |
|
107 { |
|
108 NS_DECL_ISUPPORTS |
|
109 NS_DECL_NSIOBSERVER |
|
110 }; |
|
111 |
|
112 NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver) |
|
113 |
|
114 NS_IMETHODIMP |
|
115 SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) |
|
116 { |
|
117 MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); |
|
118 MacPowerInformationService::Shutdown(); |
|
119 return NS_OK; |
|
120 } |
|
121 } // anonymous namespace |
|
122 |
|
123 /* static */ MacPowerInformationService* |
|
124 MacPowerInformationService::GetInstance() |
|
125 { |
|
126 if (sInstance) { |
|
127 return sInstance; |
|
128 } |
|
129 |
|
130 sInstance = new MacPowerInformationService(); |
|
131 |
|
132 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
133 if (obs) { |
|
134 obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false); |
|
135 } |
|
136 |
|
137 return sInstance; |
|
138 } |
|
139 |
|
140 void |
|
141 MacPowerInformationService::Shutdown() |
|
142 { |
|
143 delete sInstance; |
|
144 sInstance = nullptr; |
|
145 } |
|
146 |
|
147 MacPowerInformationService::MacPowerInformationService() |
|
148 : mRunLoopSource(nullptr) |
|
149 , mLevel(kDefaultLevel) |
|
150 , mCharging(kDefaultCharging) |
|
151 , mRemainingTime(kDefaultRemainingTime) |
|
152 , mShouldNotify(false) |
|
153 { |
|
154 // IOPSGetTimeRemainingEstimate (and the related constants) are only available |
|
155 // on 10.7, so we test for their presence at runtime. |
|
156 sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL); |
|
157 if (sIOKitFramework) { |
|
158 sIOPSGetTimeRemainingEstimate = |
|
159 (IOPSGetTimeRemainingEstimateFunc)dlsym(sIOKitFramework, "IOPSGetTimeRemainingEstimate"); |
|
160 } else { |
|
161 sIOPSGetTimeRemainingEstimate = nullptr; |
|
162 } |
|
163 } |
|
164 |
|
165 MacPowerInformationService::~MacPowerInformationService() |
|
166 { |
|
167 MOZ_ASSERT(!mRunLoopSource, |
|
168 "The observers have not been correctly removed! " |
|
169 "(StopListening should have been called)"); |
|
170 |
|
171 if (sIOKitFramework) { |
|
172 dlclose(sIOKitFramework); |
|
173 } |
|
174 } |
|
175 |
|
176 void |
|
177 MacPowerInformationService::BeginListening() |
|
178 { |
|
179 // Set ourselves up to be notified about changes. |
|
180 MOZ_ASSERT(!mRunLoopSource, "IOPS Notification Loop Source already set up. " |
|
181 "(StopListening should have been called)"); |
|
182 |
|
183 mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this); |
|
184 if (mRunLoopSource) { |
|
185 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource, |
|
186 kCFRunLoopDefaultMode); |
|
187 |
|
188 // Invoke our callback now so we have data if GetCurrentBatteryInformation is |
|
189 // called before a change happens. |
|
190 HandleChange(this); |
|
191 mShouldNotify = true; |
|
192 } |
|
193 } |
|
194 |
|
195 void |
|
196 MacPowerInformationService::StopListening() |
|
197 { |
|
198 MOZ_ASSERT(mRunLoopSource, "IOPS Notification Loop Source not set up. " |
|
199 "(StopListening without BeginListening)"); |
|
200 |
|
201 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource, |
|
202 kCFRunLoopDefaultMode); |
|
203 mRunLoopSource = nullptr; |
|
204 } |
|
205 |
|
206 void |
|
207 MacPowerInformationService::HandleChange(void* aContext) { |
|
208 MacPowerInformationService* power = |
|
209 static_cast<MacPowerInformationService*>(aContext); |
|
210 |
|
211 CFTypeRef data = ::IOPSCopyPowerSourcesInfo(); |
|
212 if (!data) { |
|
213 ::CFRelease(data); |
|
214 return; |
|
215 } |
|
216 |
|
217 // Get the list of power sources. |
|
218 CFArrayRef list = ::IOPSCopyPowerSourcesList(data); |
|
219 if (!list) { |
|
220 ::CFRelease(list); |
|
221 return; |
|
222 } |
|
223 |
|
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; |
|
229 |
|
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 = ::IOPSGetPowerSourceDescription(data, source); |
|
235 if (!currPowerSourceDesc) { |
|
236 continue; |
|
237 } |
|
238 |
|
239 // Get a battery level estimate. This key is required. |
|
240 int currentCapacity = 0; |
|
241 const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSCurrentCapacityKey)); |
|
242 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, ¤tCapacity); |
|
243 |
|
244 // This key is also required. |
|
245 int maxCapacity = 0; |
|
246 cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey)); |
|
247 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity); |
|
248 |
|
249 if (maxCapacity > 0) { |
|
250 level = static_cast<double>(currentCapacity)/static_cast<double>(maxCapacity); |
|
251 } |
|
252 |
|
253 // Find out if we're charging. |
|
254 // This key is optional, we fallback to kDefaultCharging if the current power |
|
255 // source doesn't have that info. |
|
256 if(::CFDictionaryGetValueIfPresent(currPowerSourceDesc, CFSTR(kIOPSIsChargingKey), &cfRef)) { |
|
257 charging = ::CFBooleanGetValue((CFBooleanRef)cfRef); |
|
258 |
|
259 // Get an estimate of how long it's going to take until we're fully charged. |
|
260 // This key is optional. |
|
261 if (charging) { |
|
262 // Default value that will be changed if we happen to find the actual |
|
263 // remaining time. |
|
264 remainingTime = level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime; |
|
265 |
|
266 if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc, |
|
267 CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) { |
|
268 int timeToCharge; |
|
269 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &timeToCharge); |
|
270 if (timeToCharge != kIOPSTimeRemainingUnknown) { |
|
271 remainingTime = timeToCharge*60; |
|
272 } |
|
273 } |
|
274 } else if (sIOPSGetTimeRemainingEstimate) { // not charging |
|
275 // See if we can get a time estimate. |
|
276 CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate(); |
|
277 if (estimate == kIOPSTimeRemainingUnlimited || estimate == kIOPSTimeRemainingUnknown) { |
|
278 remainingTime = kUnknownRemainingTime; |
|
279 } else { |
|
280 remainingTime = estimate; |
|
281 } |
|
282 } |
|
283 } |
|
284 |
|
285 break; |
|
286 } |
|
287 |
|
288 bool isNewData = level != power->mLevel || charging != power->mCharging || |
|
289 remainingTime != power->mRemainingTime; |
|
290 |
|
291 power->mRemainingTime = remainingTime; |
|
292 power->mCharging = charging; |
|
293 power->mLevel = level; |
|
294 |
|
295 // Notify the observers if stuff changed. |
|
296 if (power->mShouldNotify && isNewData) { |
|
297 hal::NotifyBatteryChange(hal::BatteryInformation(power->mLevel, |
|
298 power->mCharging, |
|
299 power->mRemainingTime)); |
|
300 } |
|
301 |
|
302 ::CFRelease(data); |
|
303 ::CFRelease(list); |
|
304 } |
|
305 |
|
306 } // namespace hal_impl |
|
307 } // namespace mozilla |