diff --git a/Cargo.lock b/Cargo.lock index 96679c8f..08289eb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,6 +780,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cgl" version = "0.3.2" @@ -1156,6 +1162,17 @@ dependencies = [ "libloading 0.8.4", ] +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + [[package]] name = "document-features" version = "0.2.10" @@ -1783,7 +1800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" dependencies = [ "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "cgl", "core-foundation", "dispatch", @@ -1806,7 +1823,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "glutin", "raw-window-handle 0.5.2", "winit", @@ -2348,7 +2365,7 @@ dependencies = [ "serde_yaml", "shadow-rs", "strum", - "sysinfo", + "sysinfo 0.30.13", "tracing", "tracing-appender", "tracing-subscriber", @@ -2372,8 +2389,10 @@ dependencies = [ "crossbeam-channel", "eframe", "komorebi-client", + "netdev", "serde_json", - "sysinfo", + "starship-battery", + "sysinfo 0.31.3", "windows 0.54.0", ] @@ -2418,7 +2437,7 @@ dependencies = [ "serde_json_lenient", "serde_yaml", "shadow-rs", - "sysinfo", + "sysinfo 0.30.13", "thiserror", "uds_windows", "which", @@ -2456,6 +2475,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" @@ -2561,6 +2586,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2579,6 +2613,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "memalloc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" + [[package]] name = "memchr" version = "2.7.4" @@ -2794,6 +2834,71 @@ dependencies = [ "winapi", ] +[[package]] +name = "netdev" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7516ad2c46cc25da098ed7d6b9a0cbe9e1fbffbd04b1596148b95f2841179c83" +dependencies = [ + "dlopen2", + "libc", + "memalloc", + "netlink-packet-core", + "netlink-packet-route", + "netlink-sys", + "once_cell", + "system-configuration 0.6.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +dependencies = [ + "bytes", + "libc", + "log", +] + [[package]] name = "nix" version = "0.26.4" @@ -2814,7 +2919,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", "libc", ] @@ -3295,6 +3412,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64", + "indexmap", + "quick-xml 0.32.0", + "serde", + "time", +] + [[package]] name = "png" version = "0.17.13" @@ -3421,6 +3551,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.34.0" @@ -3636,7 +3775,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tower-service", @@ -4090,6 +4229,24 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "starship-battery" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7915746794358b8f649d3032c8ce150f55b7a0cd41951f170162e82e6cf43f" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation", + "lazycell", + "libc", + "mach2", + "nix 0.29.0", + "num-traits", + "plist", + "uom", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -4200,6 +4357,20 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "sysinfo" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b92e0bdf838cbc1c4c9ba14f9c97a7ec6cdcd1ae66b10e1e42775a25553f45d" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.54.0", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -4208,7 +4379,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -4221,6 +4403,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -4696,6 +4888,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uom" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "url" version = "2.5.2" @@ -4921,7 +5123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edf466fc49a4feb65a511ca403fec3601494d0dee85dbf37fff6fa0dd4eec3b6" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.34.0", "quote", ] @@ -4983,7 +5185,7 @@ checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" dependencies = [ "arrayvec", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "document-features", "js-sys", "log", @@ -5009,7 +5211,7 @@ dependencies = [ "arrayvec", "bit-vec", "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "codespan-reporting", "document-features", "indexmap", @@ -5037,7 +5239,7 @@ dependencies = [ "arrayvec", "ash", "bitflags 2.6.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-graphics-types", "glow", "glutin_wgl_sys", @@ -5460,7 +5662,7 @@ dependencies = [ "bitflags 2.6.0", "bytemuck", "calloop", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-foundation", "core-graphics", "cursor-icon", diff --git a/komorebi-bar/Cargo.toml b/komorebi-bar/Cargo.toml index d2a83ce9..5de9d73c 100644 --- a/komorebi-bar/Cargo.toml +++ b/komorebi-bar/Cargo.toml @@ -11,7 +11,9 @@ komorebi-client = { path = "../komorebi-client" } anyhow = "1" chrono = "0.4" eframe = "0.28" +netdev = "0.30" serde_json = "1" -sysinfo = "0.30" +starship-battery = "0.9" +sysinfo = "0.31" crossbeam-channel = "0.5" windows = { workspace = true } \ No newline at end of file diff --git a/komorebi-bar/src/battery.rs b/komorebi-bar/src/battery.rs new file mode 100644 index 00000000..a21b47d8 --- /dev/null +++ b/komorebi-bar/src/battery.rs @@ -0,0 +1,81 @@ +use crate::widget::BarWidget; +use starship_battery::units::ratio::percent; +use starship_battery::Manager; +use starship_battery::State; +use std::time::Duration; +use std::time::Instant; + +#[derive(Copy, Clone, Debug)] +pub struct BatteryConfig { + pub enable: bool, +} + +impl From for Battery { + fn from(value: BatteryConfig) -> Self { + let manager = Manager::new().unwrap(); + let mut last_state = vec![]; + let mut state = None; + + if let Ok(mut batteries) = manager.batteries() { + if let Some(Ok(first)) = batteries.nth(0) { + let percentage = first.state_of_charge().get::(); + match first.state() { + State::Charging => state = Some(BatteryState::Charging), + State::Discharging => state = Some(BatteryState::Discharging), + _ => {} + } + last_state.push(format!("{percentage}%")); + } + } + + Self { + enable: value.enable, + manager, + last_state, + state: state.unwrap_or(BatteryState::Discharging), + last_updated: Instant::now(), + } + } +} + +pub enum BatteryState { + Charging, + Discharging, +} + +pub struct Battery { + pub enable: bool, + manager: Manager, + pub state: BatteryState, + last_state: Vec, + last_updated: Instant, +} + +impl BarWidget for Battery { + fn output(&mut self) -> Vec { + let mut outputs = self.last_state.clone(); + + let now = Instant::now(); + if now.duration_since(self.last_updated) > Duration::from_secs(10) { + outputs.clear(); + + if let Ok(mut batteries) = self.manager.batteries() { + if let Some(Ok(first)) = batteries.nth(0) { + let percentage = first.state_of_charge().get::(); + match first.state() { + State::Charging => self.state = BatteryState::Charging, + State::Discharging => self.state = BatteryState::Discharging, + _ => {} + } + + outputs.push(format!("{percentage}%")); + } + } + + self.last_state.clone_from(&outputs); + self.last_updated = now; + } + + outputs + } +} diff --git a/komorebi-bar/src/main.rs b/komorebi-bar/src/main.rs index 10a2dbe6..66af357a 100644 --- a/komorebi-bar/src/main.rs +++ b/komorebi-bar/src/main.rs @@ -1,16 +1,23 @@ +mod battery; mod date; mod media; mod memory; +mod network; mod storage; mod time; mod widget; +use crate::battery::Battery; +use crate::battery::BatteryConfig; +use crate::battery::BatteryState; use crate::date::Date; use crate::date::DateFormat; use crate::media::Media; use crate::media::MediaConfig; use crate::memory::Memory; use crate::memory::MemoryConfig; +use crate::network::Network; +use crate::network::NetworkConfig; use crate::storage::Storage; use crate::storage::StorageConfig; use crate::time::TimeFormat; @@ -71,6 +78,8 @@ pub struct Config { storage: StorageConfig, memory: MemoryConfig, media: MediaConfig, + battery: BatteryConfig, + network: NetworkConfig, } fn main() -> eframe::Result<()> { @@ -91,6 +100,11 @@ fn main() -> eframe::Result<()> { storage: StorageConfig { enable: true }, memory: MemoryConfig { enable: true }, media: MediaConfig { enable: true }, + battery: BatteryConfig { enable: true }, + network: NetworkConfig { + enable: true, + show_data: true, + }, }; // TODO: ensure that config.monitor_index represents a valid komorebi monitor index @@ -199,6 +213,8 @@ struct Komobar { memory: Memory, storage: Storage, media: Media, + battery: Battery, + network: Network, } impl Komobar { @@ -224,6 +240,8 @@ impl Komobar { memory: Memory::from(config.memory), storage: Storage::from(config.storage), media: Media::from(config.media), + battery: Battery::from(config.battery), + network: Network::from(config.network), } } } @@ -352,6 +370,26 @@ impl eframe::App for Komobar { // TODO: make the order configurable ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if self.battery.enable { + let battery_output = self.battery.output(); + if !battery_output.is_empty() { + for battery in battery_output { + let emoji = match self.battery.state { + BatteryState::Charging => "⚡️", + BatteryState::Discharging => "🔋", + }; + + ui.add( + Label::new(format!("{emoji} {battery}")) + .selectable(false) + .sense(Sense::click()), + ); + } + + ui.add_space(10.0); + } + } + if self.time.enable { for time in self.time.output() { if ui @@ -388,6 +426,53 @@ impl eframe::App for Komobar { ui.add_space(10.0); } + if self.network.enable { + let network_output = self.network.output(); + + if !network_output.is_empty() { + match network_output.len() { + 1 => { + if ui + .add( + Label::new(format!("📶 {}", network_output[0])) + .selectable(false) + .sense(Sense::click()), + ) + .clicked() + { + if let Err(error) = + Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() + { + eprintln!("{}", error) + } + } + } + 2 => { + if ui + .add( + Label::new(format!( + "📶 {} - {}", + network_output[0], network_output[1] + )) + .selectable(false) + .sense(Sense::click()), + ) + .clicked() + { + if let Err(error) = + Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() + { + eprintln!("{}", error) + } + }; + } + _ => {} + } + + ui.add_space(10.0); + } + } + if self.memory.enable { for ram in self.memory.output() { if ui diff --git a/komorebi-bar/src/network.rs b/komorebi-bar/src/network.rs new file mode 100644 index 00000000..d4a5a0ee --- /dev/null +++ b/komorebi-bar/src/network.rs @@ -0,0 +1,87 @@ +use crate::widget::BarWidget; +use std::time::Duration; +use std::time::Instant; +use sysinfo::Networks; + +#[derive(Copy, Clone, Debug)] +pub struct NetworkConfig { + pub enable: bool, + pub show_data: bool, +} + +impl From for Network { + fn from(value: NetworkConfig) -> Self { + let mut last_state = vec![]; + let mut networks = Networks::new_with_refreshed_list(); + + if let Ok(interface) = netdev::get_default_interface() { + if let Some(friendly_name) = interface.friendly_name { + last_state.push(friendly_name.clone()); + + if value.show_data { + networks.refresh(); + for (interface_name, data) in &networks { + if friendly_name.eq(interface_name) { + last_state.push(format!( + "{} MB (down) / {} MB (up)", + data.total_received() / 1024 / 1024, + data.total_transmitted() / 1024 / 1024, + )) + } + } + } + } + } + + Self { + enable: value.enable, + last_state, + networks, + show_data: value.show_data, + last_updated: Instant::now(), + } + } +} + +pub struct Network { + pub enable: bool, + pub show_data: bool, + networks: Networks, + last_state: Vec, + last_updated: Instant, +} + +impl BarWidget for Network { + fn output(&mut self) -> Vec { + let mut outputs = self.last_state.clone(); + + let now = Instant::now(); + if now.duration_since(self.last_updated) > Duration::from_secs(10) { + outputs.clear(); + + if let Ok(interface) = netdev::get_default_interface() { + if let Some(friendly_name) = &interface.friendly_name { + outputs.push(friendly_name.clone()); + + if self.show_data { + self.networks.refresh(); + for (interface_name, data) in &self.networks { + if friendly_name.eq(interface_name) { + outputs.push(format!( + "{} MB (down) / {} MB (up)", + data.total_received() / 1024 / 1024, + data.total_transmitted() / 1024 / 1024, + )) + } + } + } + } + } + + self.last_state.clone_from(&outputs); + self.last_updated = now; + } + + outputs + } +}