1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/cocoa/CocoaBattery.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,307 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim set: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#import <CoreFoundation/CoreFoundation.h> 1.11 +#import <IOKit/ps/IOPowerSources.h> 1.12 +#import <IOKit/ps/IOPSKeys.h> 1.13 + 1.14 +#include <mozilla/Hal.h> 1.15 +#include <mozilla/dom/battery/Constants.h> 1.16 +#include <mozilla/Services.h> 1.17 + 1.18 +#include <nsIObserverService.h> 1.19 +#include <nsIObserver.h> 1.20 + 1.21 +#include <dlfcn.h> 1.22 + 1.23 +#define IOKIT_FRAMEWORK_PATH "/System/Library/Frameworks/IOKit.framework/IOKit" 1.24 + 1.25 +#ifndef kIOPSTimeRemainingUnknown 1.26 + #define kIOPSTimeRemainingUnknown ((CFTimeInterval)-1.0) 1.27 +#endif 1.28 +#ifndef kIOPSTimeRemainingUnlimited 1.29 + #define kIOPSTimeRemainingUnlimited ((CFTimeInterval)-2.0) 1.30 +#endif 1.31 + 1.32 +using namespace mozilla::dom::battery; 1.33 + 1.34 +namespace mozilla { 1.35 +namespace hal_impl { 1.36 + 1.37 +typedef CFTimeInterval (*IOPSGetTimeRemainingEstimateFunc)(void); 1.38 + 1.39 +class MacPowerInformationService 1.40 +{ 1.41 +public: 1.42 + static MacPowerInformationService* GetInstance(); 1.43 + static void Shutdown(); 1.44 + 1.45 + void BeginListening(); 1.46 + void StopListening(); 1.47 + 1.48 + static void HandleChange(void *aContext); 1.49 + 1.50 + ~MacPowerInformationService(); 1.51 + 1.52 +private: 1.53 + MacPowerInformationService(); 1.54 + 1.55 + // The reference to the runloop that is notified of power changes. 1.56 + CFRunLoopSourceRef mRunLoopSource; 1.57 + 1.58 + double mLevel; 1.59 + bool mCharging; 1.60 + double mRemainingTime; 1.61 + bool mShouldNotify; 1.62 + 1.63 + friend void GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo); 1.64 + 1.65 + static MacPowerInformationService* sInstance; 1.66 + 1.67 + static void* sIOKitFramework; 1.68 + static IOPSGetTimeRemainingEstimateFunc sIOPSGetTimeRemainingEstimate; 1.69 +}; 1.70 + 1.71 +void* MacPowerInformationService::sIOKitFramework; 1.72 +IOPSGetTimeRemainingEstimateFunc MacPowerInformationService::sIOPSGetTimeRemainingEstimate; 1.73 + 1.74 +/* 1.75 + * Implementation of mozilla::hal_impl::EnableBatteryNotifications, 1.76 + * mozilla::hal_impl::DisableBatteryNotifications, 1.77 + * and mozilla::hal_impl::GetCurrentBatteryInformation. 1.78 + */ 1.79 + 1.80 +void 1.81 +EnableBatteryNotifications() 1.82 +{ 1.83 + MacPowerInformationService::GetInstance()->BeginListening(); 1.84 +} 1.85 + 1.86 +void 1.87 +DisableBatteryNotifications() 1.88 +{ 1.89 + MacPowerInformationService::GetInstance()->StopListening(); 1.90 +} 1.91 + 1.92 +void 1.93 +GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) 1.94 +{ 1.95 + MacPowerInformationService* powerService = MacPowerInformationService::GetInstance(); 1.96 + 1.97 + aBatteryInfo->level() = powerService->mLevel; 1.98 + aBatteryInfo->charging() = powerService->mCharging; 1.99 + aBatteryInfo->remainingTime() = powerService->mRemainingTime; 1.100 +} 1.101 + 1.102 +/* 1.103 + * Following is the implementation of MacPowerInformationService. 1.104 + */ 1.105 + 1.106 +MacPowerInformationService* MacPowerInformationService::sInstance = nullptr; 1.107 + 1.108 +namespace { 1.109 +struct SingletonDestroyer MOZ_FINAL : public nsIObserver 1.110 +{ 1.111 + NS_DECL_ISUPPORTS 1.112 + NS_DECL_NSIOBSERVER 1.113 +}; 1.114 + 1.115 +NS_IMPL_ISUPPORTS(SingletonDestroyer, nsIObserver) 1.116 + 1.117 +NS_IMETHODIMP 1.118 +SingletonDestroyer::Observe(nsISupports*, const char* aTopic, const char16_t*) 1.119 +{ 1.120 + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); 1.121 + MacPowerInformationService::Shutdown(); 1.122 + return NS_OK; 1.123 +} 1.124 +} // anonymous namespace 1.125 + 1.126 +/* static */ MacPowerInformationService* 1.127 +MacPowerInformationService::GetInstance() 1.128 +{ 1.129 + if (sInstance) { 1.130 + return sInstance; 1.131 + } 1.132 + 1.133 + sInstance = new MacPowerInformationService(); 1.134 + 1.135 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.136 + if (obs) { 1.137 + obs->AddObserver(new SingletonDestroyer(), "xpcom-shutdown", false); 1.138 + } 1.139 + 1.140 + return sInstance; 1.141 +} 1.142 + 1.143 +void 1.144 +MacPowerInformationService::Shutdown() 1.145 +{ 1.146 + delete sInstance; 1.147 + sInstance = nullptr; 1.148 +} 1.149 + 1.150 +MacPowerInformationService::MacPowerInformationService() 1.151 + : mRunLoopSource(nullptr) 1.152 + , mLevel(kDefaultLevel) 1.153 + , mCharging(kDefaultCharging) 1.154 + , mRemainingTime(kDefaultRemainingTime) 1.155 + , mShouldNotify(false) 1.156 +{ 1.157 + // IOPSGetTimeRemainingEstimate (and the related constants) are only available 1.158 + // on 10.7, so we test for their presence at runtime. 1.159 + sIOKitFramework = dlopen(IOKIT_FRAMEWORK_PATH, RTLD_LAZY | RTLD_LOCAL); 1.160 + if (sIOKitFramework) { 1.161 + sIOPSGetTimeRemainingEstimate = 1.162 + (IOPSGetTimeRemainingEstimateFunc)dlsym(sIOKitFramework, "IOPSGetTimeRemainingEstimate"); 1.163 + } else { 1.164 + sIOPSGetTimeRemainingEstimate = nullptr; 1.165 + } 1.166 +} 1.167 + 1.168 +MacPowerInformationService::~MacPowerInformationService() 1.169 +{ 1.170 + MOZ_ASSERT(!mRunLoopSource, 1.171 + "The observers have not been correctly removed! " 1.172 + "(StopListening should have been called)"); 1.173 + 1.174 + if (sIOKitFramework) { 1.175 + dlclose(sIOKitFramework); 1.176 + } 1.177 +} 1.178 + 1.179 +void 1.180 +MacPowerInformationService::BeginListening() 1.181 +{ 1.182 + // Set ourselves up to be notified about changes. 1.183 + MOZ_ASSERT(!mRunLoopSource, "IOPS Notification Loop Source already set up. " 1.184 + "(StopListening should have been called)"); 1.185 + 1.186 + mRunLoopSource = ::IOPSNotificationCreateRunLoopSource(HandleChange, this); 1.187 + if (mRunLoopSource) { 1.188 + ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), mRunLoopSource, 1.189 + kCFRunLoopDefaultMode); 1.190 + 1.191 + // Invoke our callback now so we have data if GetCurrentBatteryInformation is 1.192 + // called before a change happens. 1.193 + HandleChange(this); 1.194 + mShouldNotify = true; 1.195 + } 1.196 +} 1.197 + 1.198 +void 1.199 +MacPowerInformationService::StopListening() 1.200 +{ 1.201 + MOZ_ASSERT(mRunLoopSource, "IOPS Notification Loop Source not set up. " 1.202 + "(StopListening without BeginListening)"); 1.203 + 1.204 + ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), mRunLoopSource, 1.205 + kCFRunLoopDefaultMode); 1.206 + mRunLoopSource = nullptr; 1.207 +} 1.208 + 1.209 +void 1.210 +MacPowerInformationService::HandleChange(void* aContext) { 1.211 + MacPowerInformationService* power = 1.212 + static_cast<MacPowerInformationService*>(aContext); 1.213 + 1.214 + CFTypeRef data = ::IOPSCopyPowerSourcesInfo(); 1.215 + if (!data) { 1.216 + ::CFRelease(data); 1.217 + return; 1.218 + } 1.219 + 1.220 + // Get the list of power sources. 1.221 + CFArrayRef list = ::IOPSCopyPowerSourcesList(data); 1.222 + if (!list) { 1.223 + ::CFRelease(list); 1.224 + return; 1.225 + } 1.226 + 1.227 + // Default values. These will be used if there are 0 sources or we can't find 1.228 + // better information. 1.229 + double level = kDefaultLevel; 1.230 + double charging = kDefaultCharging; 1.231 + double remainingTime = kDefaultRemainingTime; 1.232 + 1.233 + // Look for the first battery power source to give us the information we need. 1.234 + // Usually there's only 1 available, depending on current power source. 1.235 + for (CFIndex i = 0; i < ::CFArrayGetCount(list); ++i) { 1.236 + CFTypeRef source = ::CFArrayGetValueAtIndex(list, i); 1.237 + CFDictionaryRef currPowerSourceDesc = ::IOPSGetPowerSourceDescription(data, source); 1.238 + if (!currPowerSourceDesc) { 1.239 + continue; 1.240 + } 1.241 + 1.242 + // Get a battery level estimate. This key is required. 1.243 + int currentCapacity = 0; 1.244 + const void* cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSCurrentCapacityKey)); 1.245 + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, ¤tCapacity); 1.246 + 1.247 + // This key is also required. 1.248 + int maxCapacity = 0; 1.249 + cfRef = ::CFDictionaryGetValue(currPowerSourceDesc, CFSTR(kIOPSMaxCapacityKey)); 1.250 + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberSInt32Type, &maxCapacity); 1.251 + 1.252 + if (maxCapacity > 0) { 1.253 + level = static_cast<double>(currentCapacity)/static_cast<double>(maxCapacity); 1.254 + } 1.255 + 1.256 + // Find out if we're charging. 1.257 + // This key is optional, we fallback to kDefaultCharging if the current power 1.258 + // source doesn't have that info. 1.259 + if(::CFDictionaryGetValueIfPresent(currPowerSourceDesc, CFSTR(kIOPSIsChargingKey), &cfRef)) { 1.260 + charging = ::CFBooleanGetValue((CFBooleanRef)cfRef); 1.261 + 1.262 + // Get an estimate of how long it's going to take until we're fully charged. 1.263 + // This key is optional. 1.264 + if (charging) { 1.265 + // Default value that will be changed if we happen to find the actual 1.266 + // remaining time. 1.267 + remainingTime = level == 1.0 ? kDefaultRemainingTime : kUnknownRemainingTime; 1.268 + 1.269 + if (::CFDictionaryGetValueIfPresent(currPowerSourceDesc, 1.270 + CFSTR(kIOPSTimeToFullChargeKey), &cfRef)) { 1.271 + int timeToCharge; 1.272 + ::CFNumberGetValue((CFNumberRef)cfRef, kCFNumberIntType, &timeToCharge); 1.273 + if (timeToCharge != kIOPSTimeRemainingUnknown) { 1.274 + remainingTime = timeToCharge*60; 1.275 + } 1.276 + } 1.277 + } else if (sIOPSGetTimeRemainingEstimate) { // not charging 1.278 + // See if we can get a time estimate. 1.279 + CFTimeInterval estimate = sIOPSGetTimeRemainingEstimate(); 1.280 + if (estimate == kIOPSTimeRemainingUnlimited || estimate == kIOPSTimeRemainingUnknown) { 1.281 + remainingTime = kUnknownRemainingTime; 1.282 + } else { 1.283 + remainingTime = estimate; 1.284 + } 1.285 + } 1.286 + } 1.287 + 1.288 + break; 1.289 + } 1.290 + 1.291 + bool isNewData = level != power->mLevel || charging != power->mCharging || 1.292 + remainingTime != power->mRemainingTime; 1.293 + 1.294 + power->mRemainingTime = remainingTime; 1.295 + power->mCharging = charging; 1.296 + power->mLevel = level; 1.297 + 1.298 + // Notify the observers if stuff changed. 1.299 + if (power->mShouldNotify && isNewData) { 1.300 + hal::NotifyBatteryChange(hal::BatteryInformation(power->mLevel, 1.301 + power->mCharging, 1.302 + power->mRemainingTime)); 1.303 + } 1.304 + 1.305 + ::CFRelease(data); 1.306 + ::CFRelease(list); 1.307 +} 1.308 + 1.309 +} // namespace hal_impl 1.310 +} // namespace mozilla