feat(power): add power information

This commit is contained in:
Lucien Cartier-Tilet 2025-05-24 02:38:41 +02:00
parent 3395154916
commit d0a6b6db77
Signed by: phundrak
SSH Key Fingerprint: SHA256:CE0HPsbW3L2YiJETx1zYZ2muMptaAqTN2g3498KrMkc
4 changed files with 181 additions and 5 deletions

View File

@ -12,4 +12,4 @@ anyhow = "1.0.98"
dbus = { version = "0.9.7", features = ["vendored"] }
nix = { version = "0.30.1", features = ["user"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_json = "1.0.140"

View File

@ -1,8 +1,7 @@
# pumo-system-info
A Rust-based utility for gathering and presenting system information
in JSON format. For now, it only retrieves Bluetooth-related
information.
in JSON format.
## Overview
@ -15,6 +14,7 @@ be used with my [eww](https://elkowar.github.io/eww/) configuration.
## Prerequisites
- DBus
- BlueZ (for retrieving Bluetooth-related information)
- UPower (for retrieving power information)
- Rust (specified in `rustup-toolchain.toml`)
- Nix package manager with flakes (optional, for building and managing
dependencies)

View File

@ -3,21 +3,25 @@ use dbus::blocking::Connection;
use bluetooth::BluetoothInfo;
use user_info::UserInfo;
use power::PowerInfo;
mod bluetooth;
mod user_info;
mod power;
#[derive(serde::Serialize)]
struct SystemInfo {
bluetooth: Vec<BluetoothInfo>,
user: UserInfo
user: UserInfo,
power: PowerInfo
}
fn main() -> Result<()> {
let connection = Connection::new_system()?;
let bluetooth = bluetooth::get_all_bluetooth_devices(&connection);
let user = UserInfo::new(&connection).unwrap_or_default();
let info = SystemInfo { bluetooth, user };
let power = PowerInfo::new(&connection);
let info = SystemInfo { bluetooth, user, power };
println!("{}", serde_json::to_string(&info)?);
Ok(())
}

172
src/power.rs Normal file
View File

@ -0,0 +1,172 @@
use dbus::{arg, blocking};
use std::fmt;
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(5000);
const UPOWER_DEST: &str = "org.freedesktop.UPower";
const UPOWER_DEVICE: &str = "org.freedesktop.UPower.Device";
#[derive(Debug)]
pub enum BatteryState {
Unknown,
Charging,
Discharging,
Empty,
FullyCharged,
PendingCharge,
PendingDischarge,
}
impl Default for BatteryState {
fn default() -> Self {
Self::Unknown
}
}
impl From<u32> for BatteryState {
fn from(value: u32) -> Self {
match value {
1 => Self::Charging,
2 => Self::Discharging,
3 => Self::Empty,
4 => Self::FullyCharged,
5 => Self::PendingCharge,
6 => Self::PendingDischarge,
_ => Self::Unknown,
}
}
}
impl fmt::Display for BatteryState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let str = match self {
BatteryState::Unknown => "unknown",
BatteryState::Charging => "charging",
BatteryState::Discharging => "discharging",
BatteryState::Empty => "empty",
BatteryState::FullyCharged => "fully charged",
BatteryState::PendingCharge => "pending charge",
BatteryState::PendingDischarge => "pending discharge",
};
write!(f, "{str}")
}
}
impl serde::Serialize for BatteryState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
#[derive(serde::Serialize, Default)]
pub struct BatteryInfo {
pub batteries: Vec<Battery>,
pub on_battery: bool,
}
impl BatteryInfo {
fn get_batteries(
connection: &blocking::Connection,
proxy: &blocking::Proxy<'_, &blocking::Connection>,
) -> Vec<Battery> {
let (batteries,): (Vec<dbus::strings::Path>,) = proxy
.method_call(UPOWER_DEST, "EnumerateDevices", ())
.unwrap_or_default();
let batteries: Vec<Battery> = batteries
.iter()
.filter_map(|path| match path.clone().into_cstring().into_string() {
Err(_) => None,
Ok(path) => {
if path.contains("BAT") {
Some(path)
} else {
None
}
}
})
.flat_map(|path| Battery::new(connection, path).ok())
.collect();
batteries
}
pub fn new(connection: &blocking::Connection) -> Self {
let proxy = connection.with_proxy(UPOWER_DEST, "/org/freedesktop/UPower", TIMEOUT);
use blocking::stdintf::org_freedesktop_dbus::Properties;
let on_battery: bool = proxy.get(UPOWER_DEST, "OnBattery").unwrap_or(false);
let batteries = Self::get_batteries(connection, &proxy);
Self {
on_battery,
batteries,
}
}
}
#[derive(serde::Serialize, Default, Debug)]
pub struct Battery {
pub level: f64,
pub icon: String,
pub time_to_empty: i64,
pub time_to_full: i64,
pub state: BatteryState,
}
impl Battery {
pub fn new(connection: &blocking::Connection, path: String) -> anyhow::Result<Self> {
let proxy = connection.with_proxy(UPOWER_DEST, path.clone(), TIMEOUT);
use blocking::stdintf::org_freedesktop_dbus::Properties;
let state: u32 = proxy.get(UPOWER_DEVICE, "State")?;
let result = Self {
level: proxy.get(UPOWER_DEVICE, "Percentage")?,
icon: proxy.get(UPOWER_DEVICE, "IconName")?,
time_to_empty: proxy.get(UPOWER_DEVICE, "TimeToEmpty").unwrap_or_default(),
time_to_full: proxy.get(UPOWER_DEVICE, "TimeToFull").unwrap_or_default(),
state: state.into(),
};
Ok(result)
}
}
#[derive(serde::Serialize, Default)]
pub struct PowerProfile {
pub current: String,
pub available: Vec<String>,
}
impl PowerProfile {
pub fn new(connection: &blocking::Connection) -> anyhow::Result<Self> {
let proxy = connection.with_proxy(
"org.freedesktop.UPower.PowerProfiles",
"/org/freedesktop/UPower/PowerProfiles",
TIMEOUT,
);
use blocking::stdintf::org_freedesktop_dbus::Properties;
let current: String = proxy.get("org.freedesktop.UPower.PowerProfiles", "ActiveProfile")?;
let available: Vec<arg::PropMap> =
proxy.get("org.freedesktop.UPower.PowerProfiles", "Profiles")?;
let available: Vec<String> = available
.iter()
.flat_map(|profile| profile.get("Profile")?.0.as_str())
.map(|profile| profile.to_owned())
.collect();
Ok(Self { current, available })
}
}
#[derive(serde::Serialize)]
pub struct PowerInfo {
pub power_profile: PowerProfile,
pub batteries: BatteryInfo,
}
impl PowerInfo {
pub fn new(connection: &blocking::Connection) -> Self {
let power_profile = PowerProfile::new(connection).unwrap_or_default();
let batteries = BatteryInfo::new(connection);
Self {
power_profile,
batteries,
}
}
}