basic battery module/service
This commit is contained in:
Generated
+360
@@ -145,6 +145,120 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.1.4",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 1.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 1.1.4",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
@@ -167,6 +281,23 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -256,6 +387,19 @@ dependencies = [
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "1.8.1"
|
||||
@@ -854,6 +998,12 @@ dependencies = [
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
@@ -861,6 +1011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -915,6 +1066,26 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
|
||||
dependencies = [
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
@@ -1082,7 +1253,10 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -1354,6 +1528,12 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hexf-parse"
|
||||
version = "0.2.1"
|
||||
@@ -2200,6 +2380,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.32.0"
|
||||
@@ -2748,6 +2937,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
@@ -2757,6 +2956,12 @@ dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -2830,6 +3035,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pipewire-native"
|
||||
version = "0.1.4"
|
||||
@@ -3529,6 +3745,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3546,6 +3763,16 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.2.0"
|
||||
@@ -4204,6 +4431,17 @@ dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -4282,6 +4520,17 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
@@ -5164,6 +5413,76 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"libc",
|
||||
"ordered-stream",
|
||||
"rustix 1.1.4",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcheapstr"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.3.3"
|
||||
@@ -5255,3 +5574,44 @@ name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow",
|
||||
"zcheapstr",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.5",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 3.0.5",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -17,6 +17,7 @@ pub struct BarbarConfig {
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Modules {
|
||||
Audio,
|
||||
Battery,
|
||||
Clock,
|
||||
Weather,
|
||||
Workspaces,
|
||||
@@ -26,6 +27,7 @@ impl Modules {
|
||||
pub fn to_string(&self) -> &str {
|
||||
match self {
|
||||
Modules::Audio => "audio",
|
||||
Modules::Battery => "battery",
|
||||
Modules::Clock => "clock",
|
||||
Modules::Weather => "weather",
|
||||
Modules::Workspaces => "workspaces",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
//! Battery service messages.
|
||||
|
||||
use crate::Service;
|
||||
|
||||
/// Route key a module subscribes under to receive battery events. One
|
||||
/// stream carries every event; the [`BatteryEvent`] payload tags which one,
|
||||
/// so modules filter on it.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum BatteryMsg {
|
||||
Events,
|
||||
}
|
||||
|
||||
impl BatteryMsg {
|
||||
/// Route key a module subscribes under to receive this service's events.
|
||||
pub fn key(self) -> Service {
|
||||
Service(match self {
|
||||
Self::Events => "battery.events",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge state of the first battery, without depending on D-Bus types
|
||||
/// outside the service itself.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
|
||||
pub enum BatteryStatus {
|
||||
#[default]
|
||||
Unknown,
|
||||
Charging,
|
||||
Discharging,
|
||||
Empty,
|
||||
Full,
|
||||
}
|
||||
|
||||
/// A battery event, published to subscribers of the events key.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum BatteryEvent {
|
||||
/// Charge percentage changed (0..=100).
|
||||
PercentageChanged(i32),
|
||||
/// Charging state changed.
|
||||
StatusChanged(BatteryStatus),
|
||||
/// Seconds until full (charging) or empty (discharging) changed;
|
||||
/// `None` when unknown or not applicable.
|
||||
EtaChanged(Option<u64>),
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
//! Messages owned by services.
|
||||
|
||||
mod battery;
|
||||
mod datetime;
|
||||
mod hyprland;
|
||||
mod pipewire;
|
||||
mod weather;
|
||||
|
||||
pub use battery::*;
|
||||
pub use datetime::*;
|
||||
pub use hyprland::*;
|
||||
pub use pipewire::*;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//! Battery module: prints the charge percentage from the battery service.
|
||||
|
||||
use iced::widget::text;
|
||||
use iced::{Element, Task};
|
||||
|
||||
use common::{BarModule, BatteryEvent, BatteryMsg, ModuleEffect, Service, Wire};
|
||||
|
||||
pub struct Battery {
|
||||
percent: Option<i32>,
|
||||
}
|
||||
|
||||
impl Battery {
|
||||
pub fn new() -> Self {
|
||||
Self { percent: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl BarModule for Battery {
|
||||
fn id(&self) -> &'static str {
|
||||
"battery"
|
||||
}
|
||||
|
||||
fn view(&self, _window_id: Option<iced::window::Id>) -> Element<'_, Wire> {
|
||||
match self.percent {
|
||||
Some(percent) => text(format!("{percent}%")).into(),
|
||||
None => text("--").into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect> {
|
||||
if let Some(BatteryEvent::PercentageChanged(percent)) = msg.downcast::<BatteryEvent>()
|
||||
{
|
||||
self.percent = Some(*percent);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn services(&self) -> Vec<Service> {
|
||||
vec![BatteryMsg::Events.key()]
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
//! a new file under `src/` plus one entry in `all()` — core never changes.
|
||||
|
||||
mod audio;
|
||||
mod battery;
|
||||
mod clock;
|
||||
mod weather;
|
||||
mod workspaces;
|
||||
@@ -15,6 +16,7 @@ pub use weather::*;
|
||||
pub fn all(module_config: Table) -> Vec<Box<dyn BarModule>> {
|
||||
vec![
|
||||
Box::new(audio::Audio::new()),
|
||||
Box::new(battery::Battery::new()),
|
||||
Box::new(clock::Clock::new(Some(module_config.clone()))),
|
||||
Box::new(weather::WeatherModule::default()),
|
||||
Box::new(workspaces::Workspaces::new(Some(module_config))),
|
||||
|
||||
@@ -9,6 +9,7 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../common" }
|
||||
zbus = "5"
|
||||
iced = { workspace = true }
|
||||
tokio = { workspace = true, features = ["net", "io-util"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
//! Battery service: follows UPower's display device over D-Bus and
|
||||
//! republishes each aspect whenever UPower pushes a change — no polling.
|
||||
//!
|
||||
//! Machines without a battery (or without UPower) simply never publish;
|
||||
//! the service keeps retrying in case UPower shows up later.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use common::{BatteryEvent, BatteryStatus, Endpoint, Inbox, Wire};
|
||||
use iced::futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use iced::Subscription;
|
||||
|
||||
/// Route-key namespace owned by this service; keys are `"battery.<kind>"`.
|
||||
pub const NAMESPACE: &str = "battery.";
|
||||
|
||||
/// How long to wait before retrying when UPower is unreachable.
|
||||
const RETRY: Duration = Duration::from_secs(30);
|
||||
|
||||
/// UPower device states (`org.freedesktop.UPower.Device.State`).
|
||||
const CHARGING: u32 = 1;
|
||||
const DISCHARGING: u32 = 2;
|
||||
const EMPTY: u32 = 3;
|
||||
const FULLY_CHARGED: u32 = 4;
|
||||
|
||||
pub struct BatteryService;
|
||||
|
||||
impl BatteryService {
|
||||
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||
Subscription::run_with(inbox, |inbox| {
|
||||
let key = inbox.key();
|
||||
// We dont need to rx any msgs from modules
|
||||
let mut _rx = inbox.take().expect("inbox receiver is taken once");
|
||||
|
||||
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||
tracing::info!(key, "battery service started");
|
||||
let mut last: Option<(i32, BatteryStatus, i64, i64)> = None;
|
||||
loop {
|
||||
match serve(&mut sender, key, &mut last).await {
|
||||
// Subscriber gone: stop the service.
|
||||
Ok(false) => return,
|
||||
Ok(true) => tracing::debug!(key, "upower stream ended; reconnecting"),
|
||||
Err(err) => tracing::debug!(key, %err, "upower unavailable; retrying"),
|
||||
}
|
||||
tokio::time::sleep(RETRY).await;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Follows the display device until UPower goes away. Returns whether the
|
||||
/// subscriber is still alive (`false` stops the service, `true` reconnects).
|
||||
async fn serve(
|
||||
sender: &mut mpsc::Sender<Wire>,
|
||||
key: &'static str,
|
||||
last: &mut Option<(i32, BatteryStatus, i64, i64)>,
|
||||
) -> Result<bool, zbus::Error> {
|
||||
let conn = zbus::Connection::system().await?;
|
||||
let display = UPowerProxy::new(&conn).await?.get_display_device().await?;
|
||||
let device = DeviceProxy::builder(&conn).path(display)?.build().await?;
|
||||
|
||||
if !emit(sender, key, last, read(&device).await?).await {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut percentage = device.receive_percentage_changed().await;
|
||||
let mut state = device.receive_state_changed().await;
|
||||
let mut time_to_empty = device.receive_time_to_empty_changed().await;
|
||||
let mut time_to_full = device.receive_time_to_full_changed().await;
|
||||
loop {
|
||||
// `last` is always `Some` here: the initial read published first.
|
||||
let (percent, status, to_full, to_empty) = last.unwrap_or_default();
|
||||
let current = tokio::select! {
|
||||
Some(change) = percentage.next() => (change.get().await?.round() as i32, status, to_full, to_empty),
|
||||
Some(change) = state.next() => (percent, map_state(change.get().await?), to_full, to_empty),
|
||||
Some(change) = time_to_empty.next() => (percent, status, to_full, change.get().await?),
|
||||
Some(change) = time_to_full.next() => (percent, status, change.get().await?, to_empty),
|
||||
else => return Ok(true),
|
||||
};
|
||||
if !emit(sender, key, last, current).await {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the aspects that differ from `last`; `false` when the subscriber
|
||||
/// is gone and the service should stop.
|
||||
async fn emit(
|
||||
sender: &mut mpsc::Sender<Wire>,
|
||||
key: &'static str,
|
||||
last: &mut Option<(i32, BatteryStatus, i64, i64)>,
|
||||
(percent, status, to_full, to_empty): (i32, BatteryStatus, i64, i64),
|
||||
) -> bool {
|
||||
let mut events = Vec::new();
|
||||
if last.map(|(p, _, _, _)| p) != Some(percent) {
|
||||
tracing::debug!(key, percent, "publishing battery percentage");
|
||||
events.push(BatteryEvent::PercentageChanged(percent));
|
||||
}
|
||||
if last.map(|(_, s, _, _)| s) != Some(status) {
|
||||
tracing::debug!(key, ?status, "publishing battery status");
|
||||
events.push(BatteryEvent::StatusChanged(status));
|
||||
}
|
||||
let old_eta = last.map(|(_, s, f, e)| eta(s, f, e)).unwrap_or_default();
|
||||
let new_eta = eta(status, to_full, to_empty);
|
||||
if old_eta != new_eta {
|
||||
tracing::debug!(key, ?new_eta, "publishing battery eta");
|
||||
events.push(BatteryEvent::EtaChanged(new_eta));
|
||||
}
|
||||
*last = Some((percent, status, to_full, to_empty));
|
||||
for event in events {
|
||||
let wire = Wire::topic(Endpoint::service(key), key, event);
|
||||
if sender.send(wire).await.is_err() {
|
||||
tracing::warn!(key, "battery send failed; stopping service");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Current percentage, status, and raw UPower time estimates of the display device.
|
||||
async fn read(device: &DeviceProxy<'_>) -> Result<(i32, BatteryStatus, i64, i64), zbus::Error> {
|
||||
Ok((
|
||||
device.percentage().await?.round() as i32,
|
||||
map_state(device.state().await?),
|
||||
device.time_to_full().await?,
|
||||
device.time_to_empty().await?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Seconds until full while charging, or until empty while discharging.
|
||||
/// UPower reports 0 when there is no estimate, and neither state applies
|
||||
/// once full, empty, or unknown — all of those surface as `None`.
|
||||
fn eta(status: BatteryStatus, to_full: i64, to_empty: i64) -> Option<u64> {
|
||||
let secs = match status {
|
||||
BatteryStatus::Charging => to_full,
|
||||
BatteryStatus::Discharging => to_empty,
|
||||
_ => return None,
|
||||
};
|
||||
(secs > 0).then_some(secs as u64)
|
||||
}
|
||||
|
||||
/// UPower reports pending/unknown states our protocol has no variant for;
|
||||
/// those surface as [`BatteryStatus::Unknown`].
|
||||
fn map_state(state: u32) -> BatteryStatus {
|
||||
match state {
|
||||
CHARGING => BatteryStatus::Charging,
|
||||
DISCHARGING => BatteryStatus::Discharging,
|
||||
EMPTY => BatteryStatus::Empty,
|
||||
FULLY_CHARGED => BatteryStatus::Full,
|
||||
_ => BatteryStatus::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
#[zbus::proxy(
|
||||
interface = "org.freedesktop.UPower",
|
||||
default_service = "org.freedesktop.UPower",
|
||||
default_path = "/org/freedesktop/UPower"
|
||||
)]
|
||||
trait UPower {
|
||||
/// Path of the composite display device aggregating all batteries.
|
||||
fn get_display_device(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
|
||||
}
|
||||
|
||||
#[zbus::proxy(
|
||||
interface = "org.freedesktop.UPower.Device",
|
||||
default_service = "org.freedesktop.UPower"
|
||||
)]
|
||||
trait Device {
|
||||
/// Charge percentage (0..=100).
|
||||
#[zbus(property)]
|
||||
fn percentage(&self) -> zbus::Result<f64>;
|
||||
|
||||
/// Device state (1 = charging, 2 = discharging, 3 = empty, 4 = full).
|
||||
#[zbus(property)]
|
||||
fn state(&self) -> zbus::Result<u32>;
|
||||
|
||||
/// Seconds until empty (0 when unknown).
|
||||
#[zbus(property)]
|
||||
fn time_to_empty(&self) -> zbus::Result<i64>;
|
||||
|
||||
/// Seconds until full (0 when unknown).
|
||||
#[zbus(property)]
|
||||
fn time_to_full(&self) -> zbus::Result<i64>;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use iced::Subscription;
|
||||
|
||||
use common::{Inbox, Service, Wire};
|
||||
|
||||
pub mod battery;
|
||||
pub mod compositors;
|
||||
pub mod datetime;
|
||||
pub mod pipewire;
|
||||
@@ -24,6 +25,7 @@ impl IntoSubscription for Service {
|
||||
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||
let key = self.0;
|
||||
match key {
|
||||
k if k.starts_with(battery::NAMESPACE) => battery::BatteryService::run(inbox),
|
||||
k if k.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||
k if k.starts_with(compositors::hyprland::NAMESPACE) => {
|
||||
compositors::hyprland::HyprlandService::run(inbox)
|
||||
|
||||
Reference in New Issue
Block a user