battery: first attempt using upower
This commit is contained in:
parent
66a117dfcc
commit
fb76f49325
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -712,6 +712,17 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
|
@ -1032,6 +1043,8 @@ dependencies = [
|
|||
"local-ip-address",
|
||||
"log",
|
||||
"maxminddb",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"pretty_env_logger",
|
||||
"reqwest",
|
||||
"serde",
|
||||
|
|
|
@ -17,6 +17,8 @@ futures = { version = "0.3", default-features = false }
|
|||
local-ip-address = "0.5"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
pretty_env_logger = "0.5"
|
||||
reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
1
build.rs
1
build.rs
|
@ -16,6 +16,7 @@ fn main() {
|
|||
"org.freedesktop.NetworkManager.AccessPoint.xml",
|
||||
"org.freedesktop.NetworkManager.Connection.Active.xml",
|
||||
"org.freedesktop.NetworkManager.xml",
|
||||
"org.freedesktop.UPower.Device.xml",
|
||||
"org.freedesktop.hostname1.xml",
|
||||
]);
|
||||
let required_interfaces = required_files
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
pkgs = import nixpkgs { inherit system; };
|
||||
inherit (pkgs) lib;
|
||||
dbusPaths = lib.makeSearchPathOutput "out" "share/dbus-1/interfaces"
|
||||
(with pkgs; [ systemd networkmanager ]);
|
||||
(with pkgs; [ systemd networkmanager upower ]);
|
||||
in rec {
|
||||
packages.rustybar = with pkgs;
|
||||
rustPlatform.buildRustPackage {
|
||||
|
|
|
@ -226,7 +226,7 @@ pub fn launch_tile(
|
|||
) -> JoinHandle<()> {
|
||||
let output_chan = OutputChannel::with_random_uuid(sender);
|
||||
match tile.clone() {
|
||||
TileConfig::Battery(c) => spawn(tiles::battery(c, output_chan), tile_id),
|
||||
TileConfig::Battery(c) => spawn(tiles::battery(c, output_chan, dbus_conn.clone()), tile_id),
|
||||
TileConfig::Hostname(c) => {
|
||||
spawn(tiles::hostname(c, output_chan, dbus_conn.clone()), tile_id)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ static BATTERY_CHARGING_SYMBOLS: &[char] = &[
|
|||
'\u{f0085}', // nf-md-battery_charging_100 ()
|
||||
];
|
||||
|
||||
pub fn charging_symbol(percentage: f64, charging: bool) -> char {
|
||||
pub fn battery_symbol(percentage: f64, charging: bool) -> char {
|
||||
let index = (percentage.clamp(0.0, 100.0) / 10.0).round() as usize;
|
||||
if charging {
|
||||
BATTERY_CHARGING_SYMBOLS[index]
|
||||
|
|
|
@ -22,6 +22,7 @@ async fn main() -> eyre::Result<()> {
|
|||
let err = resource.await;
|
||||
panic!("Lost connection to D-Bus: {}", err);
|
||||
});
|
||||
dbus_conn.set_signal_match_mode(true);
|
||||
|
||||
let mut stream_map = StreamMap::new();
|
||||
|
||||
|
|
|
@ -1,57 +1,156 @@
|
|||
#![allow(clippy::option_map_unit_fn)]
|
||||
use crate::config::BatteryConfig;
|
||||
use crate::formatting::charging_symbol;
|
||||
use crate::formatting::battery_symbol;
|
||||
use crate::generated::OrgFreedesktopUPowerDevice;
|
||||
use crate::output::OutputChannel;
|
||||
use crate::tile::Block;
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use tokio::fs::try_exists;
|
||||
use dbus::arg::RefArg;
|
||||
use dbus::arg::Variant;
|
||||
use dbus::message::SignalArgs;
|
||||
use dbus::nonblock::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
|
||||
use dbus::nonblock::{Proxy, SyncConnection};
|
||||
use dbus::strings::BusName;
|
||||
use eyre::OptionExt;
|
||||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use std::{convert::Infallible, time::Duration};
|
||||
use tokio::sync::watch;
|
||||
use tokio::try_join;
|
||||
|
||||
pub async fn battery(config: BatteryConfig, output: OutputChannel) -> eyre::Result<Infallible> {
|
||||
let base_dir = Path::new("/sys/class/power_supply").join(&*config.battery);
|
||||
let file_prefix = if try_exists(base_dir.join("energy_now")).await? {
|
||||
"energy_"
|
||||
} else {
|
||||
"charge_"
|
||||
};
|
||||
let now_path = base_dir.join(file_prefix.to_owned() + "now");
|
||||
let full_path = base_dir.join(file_prefix.to_owned() + "full");
|
||||
let status_path = base_dir.join("status");
|
||||
|
||||
let mut interval = tokio::time::interval(config.update);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
async fn read_and_parse<T: FromStr + Sync>(path: &Path) -> eyre::Result<T>
|
||||
where
|
||||
<T as FromStr>::Err: Send + Sync + std::error::Error + 'static,
|
||||
{
|
||||
Ok(tokio::fs::read_to_string(path).await?.trim_end().parse()?)
|
||||
#[derive(FromPrimitive, Debug, PartialEq, Eq)]
|
||||
enum DeviceState {
|
||||
Unknown = 0,
|
||||
Charging = 1,
|
||||
Discharging = 2,
|
||||
Empty = 3,
|
||||
FullyCharged = 4,
|
||||
PendingCharge = 5,
|
||||
}
|
||||
|
||||
let charge_now = read_and_parse::<f64>(&now_path);
|
||||
let charge_total = read_and_parse::<f64>(&full_path);
|
||||
let status = read_and_parse::<String>(&status_path);
|
||||
let (charge_now, charge_total, status) = try_join!(charge_now, charge_total, status)?;
|
||||
#[derive(Debug)]
|
||||
struct BatteryInfo {
|
||||
pub percentage: f64,
|
||||
pub state: DeviceState,
|
||||
pub time_to_empty: Duration,
|
||||
pub time_to_full: Duration,
|
||||
}
|
||||
|
||||
let percentage = charge_now * 100.0 / charge_total;
|
||||
let is_charging = status != "Discharging";
|
||||
let block = if config.use_symbols {
|
||||
let symbol = charging_symbol(percentage, is_charging);
|
||||
fn format_duration(dur: Duration) -> String {
|
||||
let hours = dur.as_secs() / 60 / 60;
|
||||
let minutes = (dur.as_secs() / 60) % 60;
|
||||
format!(" ({}:{:02})", hours, minutes)
|
||||
}
|
||||
|
||||
pub async fn battery(
|
||||
config: BatteryConfig,
|
||||
output: OutputChannel,
|
||||
dbus_conn: Arc<SyncConnection>,
|
||||
) -> eyre::Result<Infallible> {
|
||||
let dest = Box::leak(Box::new(BusName::new("org.freedesktop.UPower").unwrap()));
|
||||
let path = Box::leak(Box::new(
|
||||
dbus::Path::new("/org/freedesktop/UPower/devices/DisplayDevice").unwrap(),
|
||||
));
|
||||
let proxy = Proxy::new(
|
||||
dest.clone(),
|
||||
path.clone(),
|
||||
Duration::from_secs(5),
|
||||
dbus_conn.as_ref(),
|
||||
);
|
||||
|
||||
let init_info = {
|
||||
let (percentage, raw_state, time_to_empty, time_to_full) = try_join!(
|
||||
proxy.percentage(),
|
||||
proxy.state(),
|
||||
proxy.time_to_empty(),
|
||||
proxy.time_to_full()
|
||||
)?;
|
||||
BatteryInfo {
|
||||
percentage,
|
||||
state: DeviceState::from_u32(raw_state).ok_or_eyre("Invalid state")?,
|
||||
time_to_empty: Duration::from_secs(time_to_empty.clamp(0, i64::MAX).try_into()?),
|
||||
time_to_full: Duration::from_secs(time_to_full.clamp(0, i64::MAX).try_into()?),
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("{:#?}", init_info);
|
||||
|
||||
let (tx, mut rx) = watch::channel(init_info);
|
||||
|
||||
let _reciever = dbus_conn
|
||||
.add_match(PropertiesPropertiesChanged::match_rule(
|
||||
Some(dest),
|
||||
Some(path),
|
||||
))
|
||||
.await?
|
||||
.cb(move |_, changed: PropertiesPropertiesChanged| {
|
||||
tx.send_modify(|info| {
|
||||
let props = changed.changed_properties;
|
||||
props
|
||||
.get("Percentage")
|
||||
.and_then(Variant::as_f64)
|
||||
.map(|val| info.percentage = val);
|
||||
|
||||
props
|
||||
.get("State")
|
||||
.and_then(Variant::as_u64)
|
||||
.and_then(DeviceState::from_u64)
|
||||
.map(|val| info.state = val);
|
||||
|
||||
props
|
||||
.get("TimeToEmpty")
|
||||
.and_then(Variant::as_i64)
|
||||
.map(|val| val.clamp(0, i64::MAX).try_into().unwrap())
|
||||
.map(Duration::from_secs)
|
||||
.map(|val| info.time_to_empty = val);
|
||||
|
||||
props
|
||||
.get("TimeToFull")
|
||||
.and_then(Variant::as_i64)
|
||||
.map(|val| val.clamp(0, i64::MAX).try_into().unwrap())
|
||||
.map(Duration::from_secs)
|
||||
.map(|val| info.time_to_full = val);
|
||||
});
|
||||
true
|
||||
});
|
||||
|
||||
loop {
|
||||
let block = {
|
||||
let info = rx.borrow();
|
||||
let time_str = match info.state {
|
||||
DeviceState::Discharging => format_duration(info.time_to_empty),
|
||||
DeviceState::Charging => format_duration(info.time_to_full),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
if config.use_symbols {
|
||||
let symbol = match info.state {
|
||||
DeviceState::Charging | DeviceState::FullyCharged => {
|
||||
battery_symbol(info.percentage, true)
|
||||
}
|
||||
DeviceState::Discharging | DeviceState::Empty => {
|
||||
battery_symbol(info.percentage, false)
|
||||
}
|
||||
DeviceState::Unknown => '\u{f0091}',
|
||||
DeviceState::PendingCharge => '\u{f1211}',
|
||||
};
|
||||
Block {
|
||||
full_text: format!("{} {:.0}%", symbol, percentage).into(),
|
||||
full_text: format!("{} {:.0}%{}", symbol, info.percentage, time_str).into(),
|
||||
short_text: symbol.to_string().into_boxed_str().into(),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
Block {
|
||||
full_text: format!("{:.0}% {}", percentage, status).into(),
|
||||
short_text: format!("{:.0}%", percentage).into_boxed_str().into(),
|
||||
full_text: format!("{:.0}% {:?}{}", info.percentage, info.state, time_str)
|
||||
.into(),
|
||||
short_text: format!("{:.0}%", info.percentage).into_boxed_str().into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
output.send(block).await?;
|
||||
|
||||
rx.changed().await?;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue