1 // Copyright 2020 The Chromium OS 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 use crate::{BatteryData, BatteryStatus, PowerData, PowerMonitor}; 6 7 mod proto; 8 use proto::system_api::power_supply_properties::{ 9 PowerSupplyProperties, PowerSupplyProperties_BatteryState, PowerSupplyProperties_ExternalPower, 10 }; 11 12 use dbus::ffidisp::{BusType, Connection, ConnectionItem, WatchEvent}; 13 14 use protobuf::error::ProtobufError; 15 use protobuf::Message; 16 use remain::sorted; 17 use thiserror::Error; 18 19 use std::error::Error; 20 use std::os::unix::io::RawFd; 21 22 // Interface name from power_manager/dbus_bindings/org.chromium.PowerManager.xml. 23 const POWER_INTERFACE_NAME: &str = "org.chromium.PowerManager"; 24 25 // Signal name from power_manager/dbus_constants.h. 26 const POLL_SIGNAL_NAME: &str = "PowerSupplyPoll"; 27 28 impl From<PowerSupplyProperties> for PowerData { from(props: PowerSupplyProperties) -> Self29 fn from(props: PowerSupplyProperties) -> Self { 30 let ac_online = if props.has_external_power() { 31 props.get_external_power() != PowerSupplyProperties_ExternalPower::DISCONNECTED 32 } else { 33 false 34 }; 35 36 let battery = if props.has_battery_state() 37 && props.get_battery_state() != PowerSupplyProperties_BatteryState::NOT_PRESENT 38 { 39 let status = match props.get_battery_state() { 40 PowerSupplyProperties_BatteryState::FULL => BatteryStatus::NotCharging, 41 PowerSupplyProperties_BatteryState::CHARGING => BatteryStatus::Charging, 42 PowerSupplyProperties_BatteryState::DISCHARGING => BatteryStatus::Discharging, 43 _ => BatteryStatus::Unknown, 44 }; 45 46 let percent = std::cmp::min(100, props.get_battery_percent().round() as u32); 47 // Convert from volts to microvolts. 48 let voltage = (props.get_battery_voltage() * 1_000_000f64).round() as u32; 49 // Convert from amps to microamps. 50 let current = (props.get_battery_current() * 1_000_000f64).round() as u32; 51 // Convert from ampere-hours to micro ampere-hours. 52 let charge_counter = (props.get_battery_charge() * 1_000_000f64).round() as u32; 53 let charge_full = (props.get_battery_charge_full() * 1_000_000f64).round() as u32; 54 55 Some(BatteryData { 56 status, 57 percent, 58 voltage, 59 current, 60 charge_counter, 61 charge_full, 62 }) 63 } else { 64 None 65 }; 66 67 Self { ac_online, battery } 68 } 69 } 70 71 #[sorted] 72 #[derive(Error, Debug)] 73 pub enum DBusMonitorError { 74 #[error("failed to convert protobuf message: {0}")] 75 ConvertProtobuf(ProtobufError), 76 #[error("failed to add D-Bus match rule: {0}")] 77 DBusAddMatch(dbus::Error), 78 #[error("failed to connect to D-Bus: {0}")] 79 DBusConnect(dbus::Error), 80 #[error("failed to read D-Bus message: {0}")] 81 DBusRead(dbus::arg::TypeMismatchError), 82 #[error("multiple D-Bus fds")] 83 MultipleDBusFd, 84 #[error("no D-Bus fd")] 85 NoDBusFd, 86 } 87 88 pub struct DBusMonitor { 89 connection: Connection, 90 connection_fd: RawFd, 91 } 92 93 impl DBusMonitor { 94 /// Connects and configures a new D-Bus connection to listen for powerd's PowerSupplyPoll 95 /// signal. connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>>96 pub fn connect() -> std::result::Result<Box<dyn PowerMonitor>, Box<dyn Error>> { 97 let connection = 98 Connection::get_private(BusType::System).map_err(DBusMonitorError::DBusConnect)?; 99 connection 100 .add_match(&format!( 101 "interface='{}',member='{}'", 102 POWER_INTERFACE_NAME, POLL_SIGNAL_NAME 103 )) 104 .map_err(DBusMonitorError::DBusAddMatch)?; 105 // Get the D-Bus connection's fd for async I/O. This should always return a single fd. 106 let fds: Vec<RawFd> = connection 107 .watch_fds() 108 .into_iter() 109 .filter(|w| w.readable()) 110 .map(|w| w.fd()) 111 .collect(); 112 if fds.is_empty() { 113 return Err(DBusMonitorError::NoDBusFd.into()); 114 } 115 if fds.len() > 1 { 116 return Err(DBusMonitorError::MultipleDBusFd.into()); 117 } 118 Ok(Box::new(Self { 119 connection, 120 connection_fd: fds[0], 121 })) 122 } 123 } 124 125 impl PowerMonitor for DBusMonitor { 126 /// Returns the newest pending `PowerData` message, if any. 127 /// Callers should poll `PowerMonitor` to determine when messages are available. read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>>128 fn read_message(&mut self) -> std::result::Result<Option<PowerData>, Box<dyn Error>> { 129 // Select the newest available power message before converting to protobuf. 130 let newest_message: Option<dbus::Message> = self 131 .connection 132 .watch_handle( 133 self.connection_fd, 134 WatchEvent::Readable as std::os::raw::c_uint, 135 ) 136 .fold(None, |last, item| match item { 137 ConnectionItem::Signal(message) => { 138 // Ignore non-matching signals: although match rules are configured, some system 139 // signals can still get through, eg. NameAcquired. 140 let interface = match message.interface() { 141 Some(i) => i, 142 None => { 143 return last; 144 } 145 }; 146 147 if &*interface != POWER_INTERFACE_NAME { 148 return last; 149 } 150 151 let member = match message.member() { 152 Some(m) => m, 153 None => { 154 return last; 155 } 156 }; 157 158 if &*member != POLL_SIGNAL_NAME { 159 return last; 160 } 161 162 Some(message) 163 } 164 _ => last, 165 }); 166 167 match newest_message { 168 Some(message) => { 169 let data_bytes: Vec<u8> = message.read1().map_err(DBusMonitorError::DBusRead)?; 170 let mut props = PowerSupplyProperties::new(); 171 props 172 .merge_from_bytes(&data_bytes) 173 .map_err(DBusMonitorError::ConvertProtobuf)?; 174 Ok(Some(props.into())) 175 } 176 None => Ok(None), 177 } 178 } 179 poll_fd(&self) -> RawFd180 fn poll_fd(&self) -> RawFd { 181 self.connection_fd 182 } 183 } 184