1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Implementation based on sample code from 6// http://developer.apple.com/library/mac/#qa/qa1340/_index.html. 7 8#include "base/power_monitor/power_monitor_device_source.h" 9 10#include "base/mac/foundation_util.h" 11#include "base/mac/scoped_cftyperef.h" 12#include "base/power_monitor/power_monitor.h" 13#include "base/power_monitor/power_monitor_source.h" 14 15#include <IOKit/IOMessage.h> 16#include <IOKit/ps/IOPSKeys.h> 17#include <IOKit/ps/IOPowerSources.h> 18#include <IOKit/pwr_mgt/IOPMLib.h> 19 20namespace base { 21 22void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) { 23 PowerMonitorSource::ProcessPowerEvent(event); 24} 25 26bool PowerMonitorDeviceSource::IsOnBatteryPowerImpl() { 27 base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo()); 28 if (!info) 29 return false; 30 base::ScopedCFTypeRef<CFArrayRef> power_sources_list( 31 IOPSCopyPowerSourcesList(info)); 32 if (!power_sources_list) 33 return false; 34 35 bool found_battery = false; 36 const CFIndex count = CFArrayGetCount(power_sources_list); 37 for (CFIndex i = 0; i < count; ++i) { 38 const CFDictionaryRef description = IOPSGetPowerSourceDescription( 39 info, CFArrayGetValueAtIndex(power_sources_list, i)); 40 if (!description) 41 continue; 42 43 CFStringRef current_state = base::mac::GetValueFromDictionary<CFStringRef>( 44 description, CFSTR(kIOPSPowerSourceStateKey)); 45 if (!current_state) 46 continue; 47 48 // We only report "on battery power" if no source is on AC power. 49 if (CFStringCompare(current_state, CFSTR(kIOPSOffLineValue), 0) == 50 kCFCompareEqualTo) { 51 continue; 52 } else if (CFStringCompare(current_state, CFSTR(kIOPSACPowerValue), 0) == 53 kCFCompareEqualTo) { 54 return false; 55 } 56 57 DCHECK_EQ(CFStringCompare(current_state, CFSTR(kIOPSBatteryPowerValue), 0), 58 kCFCompareEqualTo) 59 << "Power source state is not one of 3 documented values"; 60 61 found_battery = true; 62 } 63 64 // At this point, either there were no readable batteries found, in which case 65 // this Mac is not on battery power, or all the readable batteries were on 66 // battery power, in which case, count this as being on battery power. 67 return found_battery; 68} 69 70PowerObserver::DeviceThermalState 71PowerMonitorDeviceSource::GetCurrentThermalState() { 72 if (@available(macOS 10.10.3, *)) { 73 return thermal_state_observer_->GetCurrentThermalState(); 74 }; 75 return PowerObserver::DeviceThermalState::kUnknown; 76} 77 78namespace { 79 80void BatteryEventCallback(void*) { 81 ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT); 82} 83 84} // namespace 85 86void PowerMonitorDeviceSource::PlatformInit() { 87 power_manager_port_ = IORegisterForSystemPower( 88 this, 89 mac::ScopedIONotificationPortRef::Receiver(notification_port_).get(), 90 &SystemPowerEventCallback, ¬ifier_); 91 DCHECK_NE(power_manager_port_, IO_OBJECT_NULL); 92 93 // Add the sleep/wake notification event source to the runloop. 94 CFRunLoopAddSource( 95 CFRunLoopGetCurrent(), 96 IONotificationPortGetRunLoopSource(notification_port_.get()), 97 kCFRunLoopCommonModes); 98 99 // Create and add the power-source-change event source to the runloop. 100 power_source_run_loop_source_.reset( 101 IOPSNotificationCreateRunLoopSource(&BatteryEventCallback, nullptr)); 102 // Verify that the source was created. This may fail if the sandbox does not 103 // permit the process to access the underlying system service. See 104 // https://crbug.com/897557 for an example of such a configuration bug. 105 DCHECK(power_source_run_loop_source_); 106 107 CFRunLoopAddSource(CFRunLoopGetCurrent(), power_source_run_loop_source_, 108 kCFRunLoopDefaultMode); 109 110 if (@available(macOS 10.10.3, *)) { 111 thermal_state_observer_ = std::make_unique<ThermalStateObserverMac>( 112 BindRepeating(&PowerMonitorSource::ProcessThermalEvent)); 113 }; 114} 115 116void PowerMonitorDeviceSource::PlatformDestroy() { 117 CFRunLoopRemoveSource( 118 CFRunLoopGetCurrent(), 119 IONotificationPortGetRunLoopSource(notification_port_.get()), 120 kCFRunLoopCommonModes); 121 122 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), 123 power_source_run_loop_source_.get(), 124 kCFRunLoopDefaultMode); 125 126 // Deregister for system power notifications. 127 IODeregisterForSystemPower(¬ifier_); 128 129 // Close the connection to the IOPMrootDomain that was opened in 130 // PlatformInit(). 131 IOServiceClose(power_manager_port_); 132 power_manager_port_ = IO_OBJECT_NULL; 133} 134 135void PowerMonitorDeviceSource::SystemPowerEventCallback( 136 void* refcon, 137 io_service_t service, 138 natural_t message_type, 139 void* message_argument) { 140 auto* thiz = static_cast<PowerMonitorDeviceSource*>(refcon); 141 142 switch (message_type) { 143 // If this message is not handled the system may delay sleep for 30 seconds. 144 case kIOMessageCanSystemSleep: 145 IOAllowPowerChange(thiz->power_manager_port_, 146 reinterpret_cast<intptr_t>(message_argument)); 147 break; 148 case kIOMessageSystemWillSleep: 149 ProcessPowerEventHelper(PowerMonitorSource::SUSPEND_EVENT); 150 IOAllowPowerChange(thiz->power_manager_port_, 151 reinterpret_cast<intptr_t>(message_argument)); 152 break; 153 154 case kIOMessageSystemWillPowerOn: 155 ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT); 156 break; 157 } 158} 159 160} // namespace base 161