| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Implementation based on sample code from |
| // http://developer.apple.com/library/mac/#qa/qa1340/_index.html. |
| |
| #include "base/power_monitor/power_monitor_device_source.h" |
| |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/power_monitor/power_monitor.h" |
| #include "base/power_monitor/power_monitor_source.h" |
| |
| #include <IOKit/IOMessage.h> |
| #include <IOKit/ps/IOPSKeys.h> |
| #include <IOKit/ps/IOPowerSources.h> |
| #include <IOKit/pwr_mgt/IOPMLib.h> |
| |
| namespace base { |
| |
| void ProcessPowerEventHelper(PowerMonitorSource::PowerEvent event) { |
| PowerMonitorSource::ProcessPowerEvent(event); |
| } |
| |
| bool PowerMonitorDeviceSource::IsOnBatteryPower() { |
| base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo()); |
| if (!info) |
| return false; |
| base::ScopedCFTypeRef<CFArrayRef> power_sources_list( |
| IOPSCopyPowerSourcesList(info)); |
| if (!power_sources_list) |
| return false; |
| |
| bool found_battery = false; |
| const CFIndex count = CFArrayGetCount(power_sources_list); |
| for (CFIndex i = 0; i < count; ++i) { |
| const CFDictionaryRef description = IOPSGetPowerSourceDescription( |
| info, CFArrayGetValueAtIndex(power_sources_list, i)); |
| if (!description) |
| continue; |
| |
| CFStringRef current_state = base::mac::GetValueFromDictionary<CFStringRef>( |
| description, CFSTR(kIOPSPowerSourceStateKey)); |
| if (!current_state) |
| continue; |
| |
| // We only report "on battery power" if no source is on AC power. |
| if (CFStringCompare(current_state, CFSTR(kIOPSOffLineValue), 0) == |
| kCFCompareEqualTo) { |
| continue; |
| } else if (CFStringCompare(current_state, CFSTR(kIOPSACPowerValue), 0) == |
| kCFCompareEqualTo) { |
| return false; |
| } |
| |
| DCHECK_EQ(CFStringCompare(current_state, CFSTR(kIOPSBatteryPowerValue), 0), |
| kCFCompareEqualTo) |
| << "Power source state is not one of 3 documented values"; |
| |
| found_battery = true; |
| } |
| |
| // At this point, either there were no readable batteries found, in which case |
| // this Mac is not on battery power, or all the readable batteries were on |
| // battery power, in which case, count this as being on battery power. |
| return found_battery; |
| } |
| |
| PowerThermalObserver::DeviceThermalState |
| PowerMonitorDeviceSource::GetCurrentThermalState() { |
| return thermal_state_observer_->GetCurrentThermalState(); |
| } |
| |
| namespace { |
| |
| void BatteryEventCallback(void*) { |
| ProcessPowerEventHelper(PowerMonitorSource::POWER_STATE_EVENT); |
| } |
| |
| } // namespace |
| |
| void PowerMonitorDeviceSource::PlatformInit() { |
| power_manager_port_ = IORegisterForSystemPower( |
| this, |
| mac::ScopedIONotificationPortRef::Receiver(notification_port_).get(), |
| &SystemPowerEventCallback, ¬ifier_); |
| DCHECK_NE(power_manager_port_, IO_OBJECT_NULL); |
| |
| // Add the sleep/wake notification event source to the runloop. |
| CFRunLoopAddSource( |
| CFRunLoopGetCurrent(), |
| IONotificationPortGetRunLoopSource(notification_port_.get()), |
| kCFRunLoopCommonModes); |
| |
| // Create and add the power-source-change event source to the runloop. |
| power_source_run_loop_source_.reset( |
| IOPSNotificationCreateRunLoopSource(&BatteryEventCallback, nullptr)); |
| // Verify that the source was created. This may fail if the sandbox does not |
| // permit the process to access the underlying system service. See |
| // https://crbug.com/897557 for an example of such a configuration bug. |
| DCHECK(power_source_run_loop_source_); |
| |
| CFRunLoopAddSource(CFRunLoopGetCurrent(), power_source_run_loop_source_, |
| kCFRunLoopDefaultMode); |
| |
| thermal_state_observer_ = std::make_unique<ThermalStateObserverMac>( |
| BindRepeating(&PowerMonitorSource::ProcessThermalEvent)); |
| } |
| |
| void PowerMonitorDeviceSource::PlatformDestroy() { |
| CFRunLoopRemoveSource( |
| CFRunLoopGetCurrent(), |
| IONotificationPortGetRunLoopSource(notification_port_.get()), |
| kCFRunLoopCommonModes); |
| |
| CFRunLoopRemoveSource(CFRunLoopGetCurrent(), |
| power_source_run_loop_source_.get(), |
| kCFRunLoopDefaultMode); |
| |
| // Deregister for system power notifications. |
| IODeregisterForSystemPower(¬ifier_); |
| |
| // Close the connection to the IOPMrootDomain that was opened in |
| // PlatformInit(). |
| IOServiceClose(power_manager_port_); |
| power_manager_port_ = IO_OBJECT_NULL; |
| } |
| |
| void PowerMonitorDeviceSource::SystemPowerEventCallback( |
| void* refcon, |
| io_service_t service, |
| natural_t message_type, |
| void* message_argument) { |
| auto* thiz = static_cast<PowerMonitorDeviceSource*>(refcon); |
| |
| switch (message_type) { |
| // If this message is not handled the system may delay sleep for 30 seconds. |
| case kIOMessageCanSystemSleep: |
| IOAllowPowerChange(thiz->power_manager_port_, |
| reinterpret_cast<intptr_t>(message_argument)); |
| break; |
| case kIOMessageSystemWillSleep: |
| ProcessPowerEventHelper(PowerMonitorSource::SUSPEND_EVENT); |
| IOAllowPowerChange(thiz->power_manager_port_, |
| reinterpret_cast<intptr_t>(message_argument)); |
| break; |
| |
| case kIOMessageSystemWillPowerOn: |
| ProcessPowerEventHelper(PowerMonitorSource::RESUME_EVENT); |
| break; |
| } |
| } |
| |
| } // namespace base |