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",
|
"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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -1032,6 +1043,8 @@ dependencies = [
|
||||||
"local-ip-address",
|
"local-ip-address",
|
||||||
"log",
|
"log",
|
||||||
"maxminddb",
|
"maxminddb",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -17,6 +17,8 @@ futures = { version = "0.3", default-features = false }
|
||||||
local-ip-address = "0.5"
|
local-ip-address = "0.5"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
maxminddb = "0.23"
|
maxminddb = "0.23"
|
||||||
|
num-derive = "0.4"
|
||||||
|
num-traits = "0.2"
|
||||||
pretty_env_logger = "0.5"
|
pretty_env_logger = "0.5"
|
||||||
reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false }
|
reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
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.AccessPoint.xml",
|
||||||
"org.freedesktop.NetworkManager.Connection.Active.xml",
|
"org.freedesktop.NetworkManager.Connection.Active.xml",
|
||||||
"org.freedesktop.NetworkManager.xml",
|
"org.freedesktop.NetworkManager.xml",
|
||||||
|
"org.freedesktop.UPower.Device.xml",
|
||||||
"org.freedesktop.hostname1.xml",
|
"org.freedesktop.hostname1.xml",
|
||||||
]);
|
]);
|
||||||
let required_interfaces = required_files
|
let required_interfaces = required_files
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
dbusPaths = lib.makeSearchPathOutput "out" "share/dbus-1/interfaces"
|
dbusPaths = lib.makeSearchPathOutput "out" "share/dbus-1/interfaces"
|
||||||
(with pkgs; [ systemd networkmanager ]);
|
(with pkgs; [ systemd networkmanager upower ]);
|
||||||
in rec {
|
in rec {
|
||||||
packages.rustybar = with pkgs;
|
packages.rustybar = with pkgs;
|
||||||
rustPlatform.buildRustPackage {
|
rustPlatform.buildRustPackage {
|
||||||
|
|
|
@ -226,7 +226,7 @@ pub fn launch_tile(
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
let output_chan = OutputChannel::with_random_uuid(sender);
|
let output_chan = OutputChannel::with_random_uuid(sender);
|
||||||
match tile.clone() {
|
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) => {
|
TileConfig::Hostname(c) => {
|
||||||
spawn(tiles::hostname(c, output_chan, dbus_conn.clone()), tile_id)
|
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 ()
|
'\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;
|
let index = (percentage.clamp(0.0, 100.0) / 10.0).round() as usize;
|
||||||
if charging {
|
if charging {
|
||||||
BATTERY_CHARGING_SYMBOLS[index]
|
BATTERY_CHARGING_SYMBOLS[index]
|
||||||
|
|
|
@ -22,6 +22,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
let err = resource.await;
|
let err = resource.await;
|
||||||
panic!("Lost connection to D-Bus: {}", err);
|
panic!("Lost connection to D-Bus: {}", err);
|
||||||
});
|
});
|
||||||
|
dbus_conn.set_signal_match_mode(true);
|
||||||
|
|
||||||
let mut stream_map = StreamMap::new();
|
let mut stream_map = StreamMap::new();
|
||||||
|
|
||||||
|
|
|
@ -1,57 +1,156 @@
|
||||||
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
use crate::config::BatteryConfig;
|
use crate::config::BatteryConfig;
|
||||||
use crate::formatting::charging_symbol;
|
use crate::formatting::battery_symbol;
|
||||||
|
use crate::generated::OrgFreedesktopUPowerDevice;
|
||||||
use crate::output::OutputChannel;
|
use crate::output::OutputChannel;
|
||||||
use crate::tile::Block;
|
use crate::tile::Block;
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use dbus::arg::RefArg;
|
||||||
use std::path::Path;
|
use dbus::arg::Variant;
|
||||||
use std::str::FromStr;
|
use dbus::message::SignalArgs;
|
||||||
use tokio::fs::try_exists;
|
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;
|
use tokio::try_join;
|
||||||
|
|
||||||
pub async fn battery(config: BatteryConfig, output: OutputChannel) -> eyre::Result<Infallible> {
|
#[derive(FromPrimitive, Debug, PartialEq, Eq)]
|
||||||
let base_dir = Path::new("/sys/class/power_supply").join(&*config.battery);
|
enum DeviceState {
|
||||||
let file_prefix = if try_exists(base_dir.join("energy_now")).await? {
|
Unknown = 0,
|
||||||
"energy_"
|
Charging = 1,
|
||||||
} else {
|
Discharging = 2,
|
||||||
"charge_"
|
Empty = 3,
|
||||||
};
|
FullyCharged = 4,
|
||||||
let now_path = base_dir.join(file_prefix.to_owned() + "now");
|
PendingCharge = 5,
|
||||||
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);
|
#[derive(Debug)]
|
||||||
loop {
|
struct BatteryInfo {
|
||||||
interval.tick().await;
|
pub percentage: f64,
|
||||||
|
pub state: DeviceState,
|
||||||
|
pub time_to_empty: Duration,
|
||||||
|
pub time_to_full: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_and_parse<T: FromStr + Sync>(path: &Path) -> eyre::Result<T>
|
fn format_duration(dur: Duration) -> String {
|
||||||
where
|
let hours = dur.as_secs() / 60 / 60;
|
||||||
<T as FromStr>::Err: Send + Sync + std::error::Error + 'static,
|
let minutes = (dur.as_secs() / 60) % 60;
|
||||||
{
|
format!(" ({}:{:02})", hours, minutes)
|
||||||
Ok(tokio::fs::read_to_string(path).await?.trim_end().parse()?)
|
}
|
||||||
|
|
||||||
|
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()?),
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let charge_now = read_and_parse::<f64>(&now_path);
|
eprintln!("{:#?}", init_info);
|
||||||
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)?;
|
|
||||||
|
|
||||||
let percentage = charge_now * 100.0 / charge_total;
|
let (tx, mut rx) = watch::channel(init_info);
|
||||||
let is_charging = status != "Discharging";
|
|
||||||
let block = if config.use_symbols {
|
let _reciever = dbus_conn
|
||||||
let symbol = charging_symbol(percentage, is_charging);
|
.add_match(PropertiesPropertiesChanged::match_rule(
|
||||||
Block {
|
Some(dest),
|
||||||
full_text: format!("{} {:.0}%", symbol, percentage).into(),
|
Some(path),
|
||||||
short_text: symbol.to_string().into_boxed_str().into(),
|
))
|
||||||
..Default::default()
|
.await?
|
||||||
}
|
.cb(move |_, changed: PropertiesPropertiesChanged| {
|
||||||
} else {
|
tx.send_modify(|info| {
|
||||||
Block {
|
let props = changed.changed_properties;
|
||||||
full_text: format!("{:.0}% {}", percentage, status).into(),
|
props
|
||||||
short_text: format!("{:.0}%", percentage).into_boxed_str().into(),
|
.get("Percentage")
|
||||||
..Default::default()
|
.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, info.percentage, time_str).into(),
|
||||||
|
short_text: symbol.to_string().into_boxed_str().into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Block {
|
||||||
|
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?;
|
output.send(block).await?;
|
||||||
|
|
||||||
|
rx.changed().await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue