CocoaBattery.cpp (9799B)
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 public: 38 static MacPowerInformationService* GetInstance(); 39 static void Shutdown(); 40 static bool IsShuttingDown(); 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( 61 hal::BatteryInformation* aBatteryInfo); 62 63 static MacPowerInformationService* sInstance; 64 static bool sShuttingDown; 65 66 static void* sIOKitFramework; 67 static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate; 68 }; 69 70 void* MacPowerInformationService::sIOKitFramework; 71 IOPSGetTimeRemainingEstimateFunc 72 MacPowerInformationService::sIOPSGetTimeRemainingEstimate; 73 74 /* 75 * Implementation of mozilla::hal_impl::EnableBatteryNotifications, 76 * mozilla::hal_impl::DisableBatteryNotifications, 77 * and mozilla::hal_impl::GetCurrentBatteryInformation. 78 */ 79 80 void EnableBatteryNotifications() { 81 if (!MacPowerInformationService::IsShuttingDown()) { 82 MacPowerInformationService::GetInstance()->BeginListening(); 83 } 84 } 85 86 void DisableBatteryNotifications() { 87 if (!MacPowerInformationService::IsShuttingDown()) { 88 MacPowerInformationService::GetInstance()->StopListening(); 89 } 90 } 91 92 void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { 93 MacPowerInformationService* powerService = 94 MacPowerInformationService::GetInstance(); 95 96 aBatteryInfo->level() = powerService->mLevel; 97 aBatteryInfo->charging() = powerService->mCharging; 98 aBatteryInfo->remainingTime() = powerService->mRemainingTime; 99 } 100 101 bool MacPowerInformationService::sShuttingDown = false; 102 103 /* 104 * Following is the implementation of MacPowerInformationService. 105 */ 106 107 MacPowerInformationService* MacPowerInformationService::sInstance = nullptr; 108 109 namespace { 110 struct SingletonDestroyer final : public nsIObserver { 111 NS_DECL_ISUPPORTS 112 NS_DECL_NSIOBSERVER 113 114 private: 115 ~SingletonDestroyer() {} 116 }; 117 118 NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver) 119 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; 125 } 126 } // namespace 127 128 /* static */ 129 MacPowerInformationService* MacPowerInformationService::GetInstance() { 130 if (sInstance) { 131 return sInstance; 132 } 133 134 sInstance = new MacPowerInformationService(); 135 136 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 137 if (obs) { 138 obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false); 139 } 140 141 return sInstance; 142 } 143 144 bool MacPowerInformationService::IsShuttingDown() { return sShuttingDown; } 145 146 void MacPowerInformationService::Shutdown() { 147 sShuttingDown = true; 148 delete sInstance; 149 sInstance = nullptr; 150 } 151 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; 166 } 167 } 168 169 MacPowerInformationService::~MacPowerInformationService() { 170 MOZ_ASSERT(!mRunLoopSource, 171 "The observers have not been correctly removed! " 172 "(StopListening should have been called)"); 173 174 if (sIOKitFramework) { 175 dlclose(sIOKitFramework); 176 } 177 } 178 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)"); 184 185 mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this); 186 if (mRunLoopSource) { 187 ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource, 188 kCFRunLoopDefaultMode); 189 190 // Invoke our callback now so we have data if GetCurrentBatteryInformation 191 // is called before a change happens. 192 HandleChange(this); 193 mShouldNotify = true; 194 } 195 } 196 197 void MacPowerInformationService::StopListening() { 198 if (mRunLoopSource) { 199 ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource, 200 kCFRunLoopDefaultMode); 201 mRunLoopSource = nullptr; 202 } 203 } 204 205 void MacPowerInformationService::HandleChange(void* aContext) { 206 MacPowerInformationService* power = 207 static_cast<MacPowerInformationService*>(aContext); 208 209 CFTypeRef data = ::IOPSCopyPowerSourcesInfo(); 210 if (!data) { 211 ::CFRelease(data); 212 return; 213 } 214 215 // Get the list of power sources. 216 CFArrayRef list = ::IOPSCopyPowerSourcesList(data); 217 if (!list) { 218 ::CFRelease(list); 219 return; 220 } 221 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; 227 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) { 235 continue; 236 } 237 238 // Get a battery level estimate. This key is required but does not always 239 // exist. 240 int currentCapacity = 0; 241 const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, 242 CFSTR(kIOPSCurrentCapacityKey)); 243 if (!cfRef) { 244 continue; 245 } 246 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, 247 ¤tCapacity); 248 249 // This key is also required. 250 int maxCapacity = 0; 251 cfRef = 252 ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey)); 253 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity); 254 255 if (maxCapacity > 0) { 256 level = static_cast<double>(currentCapacity) / 257 static_cast<double>(maxCapacity); 258 } 259 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); 266 267 // Get an estimate of how long it's going to take until we're fully 268 // charged. This key is optional. 269 if (charging) { 270 // Default value that will be changed if we happen to find the actual 271 // remaining time. 272 remainingTime = 273 level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime; 274 275 if (::CFDictionaryGetValueIfPresent( 276 currPowerSourceDesc, CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) { 277 int timeToCharge; 278 ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, 279 &timeToCharge); 280 if (timeToCharge != kIOPSTimeRemainingUnknown) { 281 remainingTime = timeToCharge * 60; 282 } 283 } 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; 290 } else { 291 remainingTime = estimate; 292 } 293 } 294 } 295 296 break; 297 } 298 299 bool isNewData = level != power->mLevel || charging != power->mCharging || 300 remainingTime != power->mRemainingTime; 301 302 power->mRemainingTime = remainingTime; 303 power->mCharging = charging; 304 power->mLevel = level; 305 306 // Notify the observers if stuff changed. 307 if (power->mShouldNotify && isNewData) { 308 hal::NotifyBatteryChange(hal::BatteryInformation( 309 power->mLevel, power->mCharging, power->mRemainingTime)); 310 } 311 312 ::CFRelease(data); 313 ::CFRelease(list); 314 } 315 316 } // namespace hal_impl 317 } // namespace mozilla