From 80b5cafb8c16295d42bc345dac806b0eb364474f Mon Sep 17 00:00:00 2001 From: jmb05 Date: Fri, 30 Oct 2026 16:49:21 +0100 Subject: [PATCH] Add Ctl Socket, iterate on Idling daemon, update sysinfo crate, start on run menu --- .gitignore | 5 + .idea/jmb-bar.iml | 1 + Cargo.lock | 135 ++++++++----------------- Cargo.toml | 12 ++- src/backend.rs | 123 ++++++++++++++++------- src/backend/brightness_control.rs | 7 +- src/backend/commands.rs | 62 ++++++++++++ src/backend/idle.rs | 159 ++++++++++++++++-------------- src/backend/perf_stats.rs | 49 ++++----- src/ctl.rs | 51 ++++++++++ src/main.rs | 4 + src/ui.rs | 33 +++++-- src/ui/action_centre.rs | 4 +- src/ui/run_menu.rs | 77 +++++++++++++++ src/ui/utilities_panel.rs | 34 +++---- style/run_menu.scss | 5 + style/style.scss | 3 +- 17 files changed, 504 insertions(+), 260 deletions(-) create mode 100644 src/backend/commands.rs create mode 100644 src/ctl.rs create mode 100644 src/ui/run_menu.rs create mode 100644 style/run_menu.scss diff --git a/.gitignore b/.gitignore index bf2d181..0d634d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ /target /unused/ +/jmb-bar +/perf.data +/qodana.yaml +/.idea/dictionaries/project.xml +/.idea/inspectionProfiles/Project_Default.xml diff --git a/.idea/jmb-bar.iml b/.idea/jmb-bar.iml index cf84ae4..afbea50 100644 --- a/.idea/jmb-bar.iml +++ b/.idea/jmb-bar.iml @@ -4,6 +4,7 @@ + diff --git a/Cargo.lock b/Cargo.lock index 0fcb012..ec3389d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ "russh", "serde", "serde_json", - "sysinfo", + "sysinfo 0.39.6", "tar", "thiserror 2.0.21", "tokio", @@ -1684,15 +1684,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futures" version = "0.3.34" @@ -2496,7 +2487,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -2737,7 +2728,7 @@ dependencies = [ "notify", "rustsni", "serde_json", - "sysinfo", + "sysinfo 0.40.0", "thiserror 2.0.21", "time-format", "tokio", @@ -2748,8 +2739,8 @@ dependencies = [ [[package]] name = "jmb-logger-rs" -version = "0.2.1" -source = "git+https://github.com/jmb05/jmb-logger-rs#ea000f8c733b7e7854fdb123b509eb484bcec101" +version = "0.2.2" +source = "git+https://github.com/jmb05/jmb-logger-rs#083f7b96a80ab43e37850735d2367e52c6b7f5b0" dependencies = [ "ansi_term", "chrono", @@ -3382,27 +3373,28 @@ dependencies = [ [[package]] name = "notify" -version = "8.2.0" +version = "8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +checksum = "82f812702fbc75d35416d3f884e04925be7e9498f4eceea2e24ba34559284674" dependencies = [ "bitflags 2.13.2", - "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "notify-types", + "objc2-core-foundation", + "objc2-core-services", "walkdir", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "notify-types" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +checksum = "0a437a9308232ce794b1e7fdf540d38ea37efc3c73492640c13b4c8568b8f108" dependencies = [ "bitflags 2.13.2", ] @@ -3553,6 +3545,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-core-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -5349,6 +5351,21 @@ dependencies = [ "windows 0.62.2", ] +[[package]] +name = "sysinfo" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110e37d98880fd804cd47509b90a8444f7aaaab655ae625e83cd442a8bc2ebf2" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows 0.62.2", +] + [[package]] name = "system-configuration" version = "0.7.0" @@ -6492,15 +6509,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -6534,30 +6542,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -6588,12 +6579,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6606,12 +6591,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6624,24 +6603,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6654,12 +6621,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6672,12 +6633,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6690,12 +6645,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -6708,12 +6657,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "1.0.4" @@ -6741,7 +6684,7 @@ dependencies = [ "serde", "thiserror 2.0.21", "windows 0.62.2", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 34d29df..d683c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,14 @@ name = "jmb-bar" version = "0.1.0" edition = "2024" +[[bin]] +name = "jmb-bar" +path = "src/main.rs" + +[[bin]] +name = "ctl" +path = "src/ctl.rs" + [dependencies] grass = "0.13.4" gtk = { version = "0.11.5", package = "gtk4", features = ["v4_24"] } @@ -17,8 +25,8 @@ tokio = { version = "1.53.3", features = ["rt", "time", "net", "sync", "rt-multi futures-util = "0.3" serde_json = "1.0.154" crossbeam-channel = "0.5.17" -jmb-logger-rs = { git = "https://github.com/jmb05/jmb-logger-rs" } -sysinfo = "0.39.6" +jmb-logger-rs = { version = "0.2.2", git = "https://github.com/jmb05/jmb-logger-rs" } +sysinfo = "0.40.0" all-smi = "0.28.1" rustsni = "0.2.2" thiserror = "2.0.21" diff --git a/src/backend.rs b/src/backend.rs index 6517691..d72075a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,5 +1,6 @@ pub mod audio; pub mod brightness_control; +mod commands; pub(crate) mod idle; pub(crate) mod json_socket; pub mod network; @@ -9,11 +10,13 @@ pub mod window_manager_hypr; use crate::LOGGER; use crate::backend::audio::{MediaBackend, MediaCommand}; use crate::backend::brightness_control::{BrightnessManager, BrightnessMessage}; +use crate::backend::commands::ControlSocket; use crate::backend::idle::{IdleDaemon, IdleMessage, IdleState}; use crate::backend::json_socket::{JsonSocketBackend, State}; use crate::backend::network::{NetworkBackendHandle, NetworkCommand, NetworkState}; use crate::backend::perf_stats::{Stats, StatsBackend}; use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent}; +use crate::ctl::ControlCommand; use crossbeam_channel::{Receiver, Sender}; use jmb_logger_rs::*; use std::fmt::Debug; @@ -31,7 +34,7 @@ pub trait BackendThread { if let Err(e) = tx.send(message.clone()) { warn!( &LOGGER, - "Error sending message to frontend: {}", + "Error sending message to frontend: {} (msg: {message:?})", e.to_string() ); } @@ -72,8 +75,20 @@ pub struct Backend { br_registrator: Registrator, stats_registrator: Registrator, media_registrator: Registrator, - tailscale_registrator: Registrator, + netbird_registrator: Registrator, idle_registrator: Registrator, + control_registrator: Registrator, +} + +pub enum ChannelType { + Net, + Hypr, + Br, + Stats, + Media, + Idle, + Netbird, + Control, } impl Backend { @@ -86,6 +101,7 @@ impl Backend { let media_registrator = MediaBackend::start(); let tailscale_registrator = JsonSocketBackend::start(); let idle_registrator = IdleDaemon::start(); + let control_registrator = ControlSocket::start(); debug!(&LOGGER, "Started Backend threads"); Self { hypr_registrator, @@ -93,47 +109,85 @@ impl Backend { br_registrator, stats_registrator, media_registrator, - tailscale_registrator, + netbird_registrator: tailscale_registrator, idle_registrator, + control_registrator, } } - pub fn subscribe(&self) -> BackendChannels { - let (net_rx, net_tx) = NetworkBackendHandle::subscribe(self.net_registrator.clone()); - let (hypr_rx, _) = HyprlandComms::subscribe(self.hypr_registrator.clone()); - let (br_rx, br_tx) = BrightnessManager::subscribe(self.br_registrator.clone()); - let (stats_rx, _) = StatsBackend::subscribe(self.stats_registrator.clone()); - let (media_rx, media_tx) = MediaBackend::subscribe(self.media_registrator.clone()); - let (tailscale_rx, _) = JsonSocketBackend::subscribe(self.tailscale_registrator.clone()); - let (idle_rx, idle_tx) = IdleDaemon::subscribe(self.idle_registrator.clone()); - BackendChannels { - net_rx: Some(net_rx), - net_tx, - hypr_rx: Some(hypr_rx), - br_rx: Some(br_rx), - br_tx, - stats_rx: Some(stats_rx), - media_rx: Some(media_rx), - media_tx, - tailscale_rx: Some(tailscale_rx), - idle_rx: Some(idle_rx), - idle_tx, + pub fn subscribe(&self, channel_types: &[ChannelType]) -> BackendChannels { + let mut channels = BackendChannels::default(); + for ch_type in channel_types { + match ch_type { + ChannelType::Net => { + let (rx, tx) = NetworkBackendHandle::subscribe(self.net_registrator.clone()); + channels.net_tx = Some(tx); + channels.net_rx = Some(rx); + } + ChannelType::Hypr => { + channels.hypr_rx = + Some(HyprlandComms::subscribe(self.hypr_registrator.clone()).0); + } + ChannelType::Br => { + let (rx, tx) = BrightnessManager::subscribe(self.br_registrator.clone()); + channels.br_rx = Some(rx); + channels.br_tx = Some(tx); + } + ChannelType::Stats => { + channels.stats_rx = + Some(StatsBackend::subscribe(self.stats_registrator.clone()).0); + } + ChannelType::Media => { + let (rx, tx) = MediaBackend::subscribe(self.media_registrator.clone()); + channels.media_rx = Some(rx); + channels.media_tx = Some(tx); + } + ChannelType::Idle => { + let (rx, tx) = IdleDaemon::subscribe(self.idle_registrator.clone()); + channels.idle_rx = Some(rx); + channels.idle_tx = Some(tx); + } + ChannelType::Netbird => { + channels.netbird_rx = + Some(JsonSocketBackend::subscribe(self.netbird_registrator.clone()).0); + } + ChannelType::Control => { + channels.ctl_rx = + Some(ControlSocket::subscribe(self.control_registrator.clone()).0); + } + } } + channels + } + + pub fn subscribe_all(&self) -> BackendChannels { + self.subscribe(&[ + ChannelType::Net, + ChannelType::Idle, + ChannelType::Netbird, + ChannelType::Br, + ChannelType::Stats, + ChannelType::Control, + ChannelType::Media, + ChannelType::Hypr, + ]) } } +#[derive(Default)] pub struct BackendChannels { net_rx: Option>, - net_tx: UnboundedSender, + net_tx: Option>, hypr_rx: Option>, br_rx: Option>, - br_tx: Sender, + br_tx: Option>, stats_rx: Option>, media_rx: Option>, - media_tx: Sender, - tailscale_rx: Option>, + media_tx: Option>, + netbird_rx: Option>, idle_rx: Option>, - idle_tx: Sender, + idle_tx: Option>, + ctl_rx: Option>, } impl BackendChannels { @@ -150,7 +204,7 @@ impl BackendChannels { self.stats_rx.take() } pub fn take_netbird_rx(&mut self) -> Option> { - self.tailscale_rx.take() + self.netbird_rx.take() } pub fn take_media_rx(&mut self) -> Option> { self.media_rx.take() @@ -158,16 +212,19 @@ impl BackendChannels { pub fn take_idle_rx(&mut self) -> Option> { self.idle_rx.take() } - pub fn net_tx(&self) -> UnboundedSender { + pub fn take_ctl_rx(&mut self) -> Option> { + self.ctl_rx.take() + } + pub fn net_tx(&self) -> Option> { self.net_tx.clone() } - pub fn br_tx(&self) -> Sender { + pub fn br_tx(&self) -> Option> { self.br_tx.clone() } - pub fn media_tx(&self) -> Sender { + pub fn media_tx(&self) -> Option> { self.media_tx.clone() } - pub fn idle_tx(&self) -> Sender { + pub fn idle_tx(&self) -> Option> { self.idle_tx.clone() } } diff --git a/src/backend/brightness_control.rs b/src/backend/brightness_control.rs index a527142..4fe132e 100644 --- a/src/backend/brightness_control.rs +++ b/src/backend/brightness_control.rs @@ -215,14 +215,19 @@ impl BackendThread> for BrightnessM } }); thread::spawn(move || { + let mut sleep_dur = 500; loop { - thread::sleep(Duration::from_millis(10_000)); + if sleep_dur < 500 { + sleep_dur += 1; + } + thread::sleep(Duration::from_millis(sleep_dur)); let mut messages = vec![]; for (monitor, data) in &mut instance.lock().expect("Couldn't get lock").display_cache { if data.update() { + sleep_dur = 50; messages.push(BrightnessMessage::Value(monitor.clone(), data.value, false)); } } diff --git a/src/backend/commands.rs b/src/backend/commands.rs new file mode 100644 index 0000000..b092de6 --- /dev/null +++ b/src/backend/commands.rs @@ -0,0 +1,62 @@ +use crate::LOGGER; +use crate::backend::{BackendThread, SimpleBackendThread}; +use crate::ctl::{ControlCommand, SOCKET_PATH}; +use crossbeam_channel::{Receiver, Sender}; +use jmb_logger_rs::*; +use std::io::Read; +use std::net::Shutdown; +use std::os::unix::net::UnixListener; +use std::sync::{Arc, Mutex}; + +pub struct ControlSocket { + subscribers: Vec>, +} + +impl SimpleBackendThread for ControlSocket { + fn subscribers(&self) -> &Vec> { + &self.subscribers + } + + fn init(registration_rx: Receiver>) { + let instance = Arc::new(Mutex::new(Self { + subscribers: Vec::new(), + })); + let instance_clone = instance.clone(); + std::thread::spawn(move || { + while let Ok(sender) = registration_rx.recv() { + instance_clone.lock().unwrap().subscribers.push(sender); + } + }); + std::thread::spawn(move || { + if std::fs::metadata(SOCKET_PATH).is_ok() { + if let Err(e) = std::fs::remove_file(SOCKET_PATH) { + error!(&LOGGER, "Error deleting old socket file: {e}"); + return; + } + } + let socket = UnixListener::bind(SOCKET_PATH); + let Ok(socket) = socket else { + error!(&LOGGER, "Error creating socket {}", socket.err().unwrap()); + return; + }; + + info!(&LOGGER, "Successfully started control socket"); + + loop { + if let Ok((mut stream, _addr)) = socket.accept() { + info!(&LOGGER, "Socket accepted connection"); + let mut read = String::new(); + let res = stream.read_to_string(&mut read); + if let Err(e) = res { + error!(&LOGGER, "Error reading: {e}"); + let _ = stream.shutdown(Shutdown::Both); + continue; + } + if let Some(command) = ControlCommand::from_str(&read) { + instance.lock().unwrap().publish(command); + } + } + } + }); + } +} diff --git a/src/backend/idle.rs b/src/backend/idle.rs index 95d044e..36ba353 100644 --- a/src/backend/idle.rs +++ b/src/backend/idle.rs @@ -6,7 +6,7 @@ use nmrs::raw::zbus::interface; use std::collections::HashMap; use std::error::Error; use std::sync::{Arc, Mutex}; -use std::time::Duration; +use std::time::{Duration, Instant}; use tokio::sync::watch; use tokio::time::sleep; use wayland_client::globals::registry_queue_init; @@ -26,7 +26,7 @@ const LEGACY_OBJECT_PATH: &str = "/ScreenSaver"; struct WlState { idle: Option, - tx: watch::Sender, + tx: watch::Sender, } delegate_noop!(WlState: ignore WlSeat); @@ -53,14 +53,18 @@ impl Dispatch for WlState { _: &QueueHandle, ) { match event { - Event::Idled => state.idle = Some(true), - Event::Resumed => state.idle = Some(false), + Event::Idled => { + println!("Idled: {:?}", Instant::now()); + state.idle = Some(true); + } + Event::Resumed => { + println!("Resumed: {:?}", Instant::now()); + state.idle = Some(false); + } _ => {} } if let Some(idle) = state.idle { - state.tx.send_modify(|state| { - state.wayland_idle = idle; - }); + state.tx.send_modify(|state| *state = idle); } } } @@ -84,20 +88,6 @@ struct Inhibitor { _reason: String, } -#[derive(Clone, Copy, Debug)] -struct IdleStateInner { - dimmed: bool, - wayland_idle: bool, - dbus_inhibited: bool, - caffeine: bool, -} - -impl IdleStateInner { - fn should_dim(&self) -> bool { - self.wayland_idle && !self.dbus_inhibited - } -} - #[derive(Debug, Clone)] pub enum IdleState { Init(Sender), @@ -107,20 +97,6 @@ pub enum IdleState { Active, } -impl From for IdleState { - fn from(value: IdleStateInner) -> Self { - if value.should_dim() { - IdleState::Dimmed - } else if value.caffeine { - IdleState::Caffeine - } else if value.dbus_inhibited { - IdleState::Inhibited - } else { - IdleState::Active - } - } -} - #[derive(Debug, Clone)] pub enum IdleMessage { Inhibit, @@ -170,15 +146,10 @@ impl BackendThread> for IdleDaemon { } } }); - let (state_tx, state_rx) = watch::channel(IdleStateInner { - dimmed: false, - wayland_idle: false, - dbus_inhibited: false, - caffeine: false, - }); - let tx_clone = state_tx.clone(); + let (wl_tx, wl_rx) = watch::channel(false); + let (dbus_tx, dbus_rx) = watch::channel((false, false)); std::thread::spawn(move || { - if let Err(err) = Self::start_idle_notifier(tx_clone) { + if let Err(err) = Self::start_idle_notifier(wl_tx) { error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}"); } }); @@ -216,10 +187,10 @@ impl BackendThread> for IdleDaemon { .expect("Failed to create Tokio runtime"); runtime.block_on(async move { - let res = IdleDaemon::start_dbus(state_tx).await; + let res = IdleDaemon::start_dbus(dbus_tx).await; match res { Ok((_, _)) => { - Self::run_idle_timer(instance, state_rx, Duration::from_secs(10)).await; + Self::run_idle_timer(instance, wl_rx, dbus_rx).await; } Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"), } @@ -228,41 +199,77 @@ impl BackendThread> for IdleDaemon { } } +#[derive(Clone, Copy, Debug)] +enum IdleStage { + Dim, + TurnOff, + Sleep, +} + +impl IdleStage { + fn next(self) -> Self { + match self { + IdleStage::Dim => IdleStage::TurnOff, + IdleStage::TurnOff => IdleStage::Sleep, + IdleStage::Sleep => IdleStage::Sleep, + } + } + + fn timeout(self) -> Duration { + match self { + Self::Dim => Duration::from_secs(20), + Self::TurnOff => Duration::from_secs(20), + Self::Sleep => Duration::from_secs(20), + } + } +} + impl IdleDaemon { async fn run_idle_timer( - instance: Arc>, - mut state_rx: watch::Receiver, - timeout: Duration, + _instance: Arc>, + mut wl_rx: watch::Receiver, + mut dbus_rx: watch::Receiver<(bool, bool)>, ) { loop { - let mut state = *state_rx.borrow_and_update(); - instance.lock().unwrap().publish(state.into()); - - if !state.should_dim() { - if state_rx.changed().await.is_err() { - return; - } - - continue; + if let Err(e) = wl_rx.changed().await { + warn!(&LOGGER, "Error getting Wayland idle state {e}"); + return; } - - tokio::select! { - _ = sleep(timeout) => { - state.dimmed = true; - instance.lock().unwrap().publish(state.into()); - } - result = state_rx.changed() => { - if result.is_err() { - return; + let wl_idle = *wl_rx.borrow_and_update(); + let dbus_state = *dbus_rx.borrow_and_update(); + println!("Wayland idle state changed {wl_idle} (DBus state: {dbus_state:?})"); + if wl_idle && !dbus_state.0 { + println!("Going into idle cycle"); + let mut stage = IdleStage::Dim; + loop { + tokio::select! { + _ = sleep(stage.timeout()) => { + stage = stage.next(); + println!("Reached next stage {stage:?}"); + } + res = wl_rx.changed() => { + if let Err(e) = res { + warn!(&LOGGER, "Error getting Wayland idle state {e}"); + return; + } + println!("WL state change"); + break; + } + res = dbus_rx.changed() => { + if let Err(e) = res { + warn!(&LOGGER, "Error getting D-Bus idle state {e}"); + return; + } + println!("D-Bus state change"); + break; + } } - state.dimmed = false; - instance.lock().unwrap().publish(state.into()); } } } } - fn start_idle_notifier(tx: watch::Sender) -> Result<(), Box> { + fn start_idle_notifier(tx: watch::Sender) -> Result<(), Box> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init::(&conn)?; let queue_handle = event_queue.handle(); @@ -281,7 +288,7 @@ impl IdleDaemon { } async fn start_dbus( - tx: watch::Sender, + tx: watch::Sender<(bool, bool)>, ) -> zbus::Result<(ScreenSaver, zbus::connection::Connection)> { let service = ScreenSaver::new(tx); @@ -345,11 +352,11 @@ struct ScreenSaver { struct Inner { next_cookie: u32, inhibitors: HashMap, - tx: watch::Sender, + tx: watch::Sender<(bool, bool)>, } impl ScreenSaver { - fn new(tx: watch::Sender) -> Self { + fn new(tx: watch::Sender<(bool, bool)>) -> Self { Self { inner: Arc::new(Mutex::new(Inner { next_cookie: 1, @@ -405,8 +412,8 @@ impl ScreenSaver { ); inner.tx.send_modify(|state| { - state.dbus_inhibited = !inner.inhibitors.is_empty(); - state.caffeine = caffeine_flag; + state.0 = !inner.inhibitors.is_empty(); + state.1 = caffeine_flag; }); Ok(cookie) } @@ -436,8 +443,8 @@ impl ScreenSaver { } } inner.tx.send_modify(|state| { - state.dbus_inhibited = !inner.inhibitors.is_empty(); - state.caffeine = caffeine_flag; + state.0 = !inner.inhibitors.is_empty(); + state.1 = caffeine_flag; }); Ok(()) } diff --git a/src/backend/perf_stats.rs b/src/backend/perf_stats.rs index 7182a54..f6396bd 100644 --- a/src/backend/perf_stats.rs +++ b/src/backend/perf_stats.rs @@ -16,8 +16,8 @@ const POLL_INTERVAL: u64 = 10_000; #[derive(Clone, Debug)] pub struct Stats { - pub cpu: u8, - pub ram: u8, + pub cpu: Option, + pub ram: Option, pub disks: Vec<(String, u8)>, pub gpus: Vec<(String, u8)>, pub battery_charge: u8, @@ -28,8 +28,8 @@ pub struct Stats { pub struct StatsBackend { subscribers: Vec>, - system: System, - disks: Disks, + system: Option, + disks: Option, } impl BackendThread for StatsBackend { @@ -47,8 +47,8 @@ impl BackendThread for StatsBackend { thread::spawn(move || { let mut instance = Self { subscribers: Vec::new(), - system: System::new(), - disks: Disks::new(), + system: System::new().ok(), + disks: Disks::new().ok(), }; let manager = battery::Manager::new(); let smi = AllSmi::new(); @@ -58,24 +58,29 @@ impl BackendThread for StatsBackend { instance.subscribers.push(tx); } - instance.system.refresh_cpu_usage(); - instance.system.refresh_memory(); - instance.disks.refresh(true); + let mut cpu = None; + let mut ram = None; + if let Some(system) = instance.system.as_mut() { + system.refresh_cpu_usage(); + system.refresh_memory(); - let cpu = instance.system.global_cpu_usage() as u8; - let ram = - (instance.system.used_memory() * 100 / instance.system.total_memory()) as u8; + cpu = Some(system.global_cpu_usage() as u8); + ram = Some((system.used_memory() * 100 / system.total_memory()) as u8); + } - let disk_stats: Vec<(String, u8)> = instance - .disks - .iter() - .map(|disk| { - let mount_point = disk.mount_point().display().to_string(); - let used_space = disk.total_space() - disk.available_space(); - let percentage = (used_space * 100 / disk.total_space()) as u8; - (mount_point, percentage) - }) - .collect(); + let mut disk_stats = Vec::new(); + if let Some(disks) = instance.disks.as_mut() { + disks.refresh(true); + disk_stats = disks + .iter() + .map(|disk| { + let mount_point = disk.mount_point().display().to_string(); + let used_space = disk.total_space() - disk.available_space(); + let percentage = (used_space * 100 / disk.total_space()) as u8; + (mount_point, percentage) + }) + .collect(); + } let mut gpus: Vec<(String, u8)> = Vec::new(); if let Ok(smi) = &smi { diff --git a/src/ctl.rs b/src/ctl.rs new file mode 100644 index 0000000..ec12cc0 --- /dev/null +++ b/src/ctl.rs @@ -0,0 +1,51 @@ +use jmb_logger_rs::*; +use std::env; +use std::io::Write; +use std::os::unix::net::UnixStream; + +const CTL_LOGGER: Logger = Logger::create_with_custom_format(Level::DEBUG, String::new()); +pub const SOCKET_PATH: &str = "/run/user/1000/jmb-bar.sock"; + +#[derive(Clone, Debug)] +pub enum ControlCommand { + OpenRunMenu, +} + +impl ControlCommand { + pub fn from_str(s: &str) -> Option { + match s { + "menu" => Some(ControlCommand::OpenRunMenu), + _ => None, + } + } + + pub fn to_string(self) -> String { + match self { + ControlCommand::OpenRunMenu => String::from("menu"), + } + } +} + +fn main() { + info!(&CTL_LOGGER, "Hello, this is mission control"); + + let socket = UnixStream::connect(SOCKET_PATH); + let Ok(mut stream) = socket else { + error!( + &CTL_LOGGER, + "Error Connecting to socket: {}", + socket.err().unwrap() + ); + error!(&CTL_LOGGER, "Is the bar running?"); + return; + }; + + let args: Vec = env::args().collect(); + + match args.get(1) { + Some(str) if let Some(cmd) = ControlCommand::from_str(str) => stream + .write(cmd.to_string().as_bytes()) + .expect("Error sending message"), + _ => 0usize, + }; +} diff --git a/src/main.rs b/src/main.rs index 3a9d783..5d7cad7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use crate::backend::Backend; mod backend; mod css; +mod ctl; mod ui; use glib::clone; @@ -29,6 +30,7 @@ fn main() { let backend = Backend::start(); app.connect_startup(move |_| { + debug!(&LOGGER, "Connect startup"); let provider = CssProvider::new(); if let Some(css) = css::parse_css() { provider.load_from_string(&css); @@ -46,10 +48,12 @@ fn main() { #[strong] backend, move |app| { + debug!(&LOGGER, "Connect activate"); trace!(&LOGGER, "Starting bars"); ui::create_bars(app, backend.clone()); } )); + debug!(&LOGGER, "Running app"); app.run(); } diff --git a/src/ui.rs b/src/ui.rs index e131592..691af0f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,7 +1,9 @@ use crate::LOGGER; use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent}; -use crate::backend::{Backend, BackendChannels}; +use crate::backend::{Backend, BackendChannels, ChannelType}; +use crate::ctl::ControlCommand; use crate::ui::action_centre::ActionCentre; +use crate::ui::run_menu::RunMenu; use crossbeam_channel::TryRecvError; use glib::ControlFlow; use glib::prelude::Cast; @@ -17,6 +19,7 @@ use std::sync::Mutex; use std::time::Duration; mod action_centre; +mod run_menu; mod systray; mod title_panel; mod utilities_panel; @@ -27,14 +30,14 @@ pub fn create_bars(app: &Application, backend: Backend) { let display = Display::default().expect("Couldn't find GDK Display"); let app_clone = app.clone(); let bars: Rc>> = Rc::new(Mutex::new(HashMap::new())); - let ac_channels = backend.subscribe(); + let ac_channels = backend.subscribe_all(); let action_centre = ActionCentre::create_action_centre(app.clone(), ac_channels); for monitor in 0..display.monitors().n_items() { debug!(&LOGGER, "Opening bar {monitor}"); - let channels = backend.subscribe(); + let channels = backend.subscribe_all(); bars.lock().expect("Couldn't get active bars").insert( monitor, - create_bar( + create_and_show_bar( app_clone.clone(), &display, monitor, @@ -43,6 +46,7 @@ pub fn create_bars(app: &Application, backend: Backend) { ), ); } + let backend_clone = backend.clone(); display .monitors() .connect_items_changed(move |_list, pos, removed, added| { @@ -55,20 +59,35 @@ pub fn create_bars(app: &Application, backend: Backend) { debug!(&LOGGER, "Detected new monitor: {pos}"); let app_clone = app_clone.clone(); let action_centre = action_centre.clone(); - let channels = backend.subscribe(); + let channels = backend_clone.subscribe_all(); let bars = bars.clone(); let display = display.clone(); glib::idle_add_local_once(move || { bars.lock().expect("Couldn't get active bars").insert( pos, - create_bar(app_clone, &display, pos, action_centre, channels), + create_and_show_bar(app_clone, &display, pos, action_centre, channels), ); }); } }); + + let mut ctl_channel = backend.subscribe(&[ChannelType::Control]); + let ctl_rx = ctl_channel.take_ctl_rx().unwrap(); + let app_clone = app.clone(); + let menu = RunMenu::create(app_clone); + glib::timeout_add_local(Duration::from_millis(100), move || { + while let Ok(cmd) = ctl_rx.try_recv() { + match cmd { + ControlCommand::OpenRunMenu => { + menu.show(); + } + } + } + ControlFlow::Continue + }); } -fn create_bar( +fn create_and_show_bar( app: Application, display: &Display, monitor_idx: u32, diff --git a/src/ui/action_centre.rs b/src/ui/action_centre.rs index 4ad9e9d..cd53fa6 100644 --- a/src/ui/action_centre.rs +++ b/src/ui/action_centre.rs @@ -51,7 +51,7 @@ impl ActionCentre { .build(); window.init_layer_shell(); - window.set_layer(Layer::Overlay); + window.set_layer(Layer::Top); window.set_anchor(Edge::Top, true); window.set_anchor(Edge::Bottom, true); window.set_anchor(Edge::Right, true); @@ -230,7 +230,7 @@ impl ActionCentre { let idx = ((network.signal as f32 / 100.0) * (WIFI_ICONS.len() - 1) as f32 + 0.5) as usize; widget.set_wifi_icon_from_name(WIFI_ICONS[idx]); borrowed_wifi_networks_clone.insert(ssid.clone(), widget.clone()); - let net_tx = channels.net_tx(); + let net_tx = channels.net_tx().unwrap(); widget.connect_clicked(clone!( #[strong] net_tx, diff --git a/src/ui/run_menu.rs b/src/ui/run_menu.rs new file mode 100644 index 0000000..1f8ab56 --- /dev/null +++ b/src/ui/run_menu.rs @@ -0,0 +1,77 @@ +use gtk::prelude::{BoxExt, GtkWindowExt, WidgetExt}; +use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk}; +use gtk4_layer_shell::{KeyboardMode, Layer, LayerShell}; + +#[derive(Clone)] +pub struct RunMenu { + window: ApplicationWindow, +} + +impl RunMenu { + pub fn create(application: Application) -> Self { + let window = gtk::ApplicationWindow::builder() + .application(&application) + .title("JmbRunMenu") + .default_width(500) + .default_height(300) + .css_classes(["run-menu"]) + .build(); + + window.init_layer_shell(); + window.set_layer(Layer::Overlay); + window.set_halign(Align::Center); + window.set_valign(Align::Center); + window.set_size_request(500, 300); + window.set_keyboard_mode(KeyboardMode::Exclusive); + + let v_box = gtk::Box::new(Orientation::Vertical, 0); + + let entry = gtk::Entry::new(); + entry.add_css_class("entry"); + + let scrolled = gtk::ScrolledWindow::new(); + scrolled.set_policy(PolicyType::Never, PolicyType::Automatic); + scrolled.set_vexpand(true); + scrolled.set_valign(Align::Fill); + + let content_box = gtk::Box::new(Orientation::Vertical, 0); + content_box.add_css_class("content-box"); + + for _ in 0..10 { + let label = gtk::Label::new(Some("Test")); + content_box.append(&label); + } + + scrolled.set_child(Some(&content_box)); + + v_box.append(&entry); + v_box.append(&scrolled); + + window.set_child(Some(&v_box)); + + let window_clone = window.clone(); + let instance = Self { + window: window_clone, + }; + let instance_clone = instance.clone(); + let key_controller = gtk::EventControllerKey::new(); + key_controller.connect_key_pressed(move |_, key, _, _| { + if key == gdk::Key::Escape { + instance_clone.hide(); + glib::Propagation::Stop + } else { + glib::Propagation::Proceed + } + }); + window.add_controller(key_controller); + instance + } + + pub fn hide(&self) { + self.window.set_visible(false); + } + + pub fn show(&self) { + self.window.present(); + } +} diff --git a/src/ui/utilities_panel.rs b/src/ui/utilities_panel.rs index 0bc0cb4..299303f 100644 --- a/src/ui/utilities_panel.rs +++ b/src/ui/utilities_panel.rs @@ -62,20 +62,11 @@ pub fn build( String::new() }; let monitor_valid = !monitor.is_empty(); - println!( - "{:?}", - monitors - .item(monitor_idx) - .unwrap() - .downcast::() - .unwrap() - .connector() - ); if monitor_valid { let _ = channels .br_tx() + .unwrap() .send(BrightnessMessage::Request(monitor.clone())); - println!("req"); } let volume_adjustment = Adjustment::new(60.0, 1.0, 100.0, 1.0, 5.0, 0.0); @@ -85,7 +76,7 @@ pub fn build( volume.imp().second_icon.set_icon_name("bluetooth-symbolic"); volume.imp().second_icon.add_child_css_class("bt-icon"); - let media_tx = channels.media_tx(); + let media_tx = channels.media_tx().unwrap(); let changed_progr = Rc::new(Cell::new(false)); let changed_progr_clone = changed_progr.clone(); volume.connect_value_changed(move |slider, button, _| { @@ -97,7 +88,7 @@ pub fn build( )); } }); - let media_tx = channels.media_tx(); + let media_tx = channels.media_tx().unwrap(); volume.connect_clicked(move |slider, button, _| { if button.is_active() { let _ = media_tx.send(MediaCommand::Cmd(slider.value() as u8, true, false)); @@ -110,7 +101,7 @@ pub fn build( let brightness = IconSlider::new(Some(brightness_adjustment)); brightness.imp().main_icon.set_font_icon(""); - let br_tx = channels.br_tx(); + let br_tx = channels.br_tx().unwrap(); brightness.connect_value_changed(clone!( #[strong] monitor, @@ -128,7 +119,7 @@ pub fn build( } } )); - let br_tx = channels.br_tx(); + let br_tx = channels.br_tx().unwrap(); brightness.connect_clicked(clone!( #[strong] monitor, @@ -144,7 +135,7 @@ pub fn build( let caffeine_button = Button::with_label(" "); caffeine_button.add_css_class("caffeine"); - let idle_tx = channels.idle_tx(); + let idle_tx = channels.idle_tx().unwrap(); caffeine_button.connect_clicked(move |button| { if button.has_css_class("on") { let _ = idle_tx.send(IdleMessage::Release); @@ -221,7 +212,7 @@ pub fn build( let wg_button = Button::with_label("wg"); wg_button.add_css_class("vpn"); - let net_tx = channels.net_tx(); + let net_tx = channels.net_tx().unwrap(); wg_button.connect_clicked(move |_| { let _ = net_tx.send(NetworkCommand::ToggleVpn(String::from("wg"))); }); @@ -328,7 +319,7 @@ pub fn build( let netbird_rx = channels.take_netbird_rx().unwrap(); let idle_rx = channels.take_idle_rx().unwrap(); let monitor_clone = monitor.clone(); - let br_tx = channels.br_tx(); + let br_tx = channels.br_tx().unwrap(); glib::timeout_add_local(Duration::from_millis(100), move || { while let Ok(command) = media_rx.try_recv() { if let MediaCommand::Cmd(vol, mute, bt) = command { @@ -374,7 +365,6 @@ pub fn build( let _ = br_tx.send(BrightnessMessage::Value(monitor_clone.clone(), 20, true)); brightness.set_value(20.0); - println!("Dimmed: {monitor_clone}"); } caffeine_button.add_css_class("idle"); caffeine_button.remove_css_class("inhibit"); @@ -474,8 +464,12 @@ pub fn build( vpn_button.remove_css_class("login"); } while let Ok(stats) = stats_rx.try_recv() { - cpu_label.set_label(&format!("{}%", stats.cpu)); - ram_label.set_label(&format!("{}%", stats.ram)); + if let Some(cpu) = stats.cpu { + cpu_label.set_label(&format!("{}%", cpu)); + } + if let Some(ram) = stats.ram { + ram_label.set_label(&format!("{}%", ram)); + } for disk in stats.disks { if disk.0.eq("/") { disk_label.set_label(&format!("{}%", disk.1)); diff --git a/style/run_menu.scss b/style/run_menu.scss new file mode 100644 index 0000000..6ca8644 --- /dev/null +++ b/style/run_menu.scss @@ -0,0 +1,5 @@ +@use "colors" as *; + +.run-menu { + background: $background; +} \ No newline at end of file diff --git a/style/style.scss b/style/style.scss index 4bcb4cb..0693e97 100644 --- a/style/style.scss +++ b/style/style.scss @@ -1,3 +1,4 @@ @use "widgets"; @use "bar"; -@use "action_centre"; \ No newline at end of file +@use "action_centre"; +@use "run_menu"; \ No newline at end of file