diff --git a/jmb-bar.conf b/jmb-bar.conf index 1eeb279..6a40441 100644 --- a/jmb-bar.conf +++ b/jmb-bar.conf @@ -1,4 +1,7 @@ +[idle] idle_timeout = 20 dim_timeout = 60 lock_timeout = 300 -sleep_timeout = 320 \ No newline at end of file +sleep_timeout = 320 +lock_command = 'hyprlock' +sleep_command = 'systemctl sleep' \ No newline at end of file diff --git a/src/backend.rs b/src/backend.rs index 452f913..49b3b34 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -17,6 +17,7 @@ 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::config::Config; use crate::ctl::ControlCommand; use crossbeam_channel::{Receiver, Sender}; use jmb_logger_rs::*; @@ -28,7 +29,7 @@ pub(crate) type Registrator = Sender>; pub trait BackendThread { fn subscribe(registrator: Registrator) -> (Receiver, D); fn subscribers(&self) -> &Vec>; - fn init(registration_rx: Receiver>); + fn init(self, registration_rx: Receiver>); fn publish(&self, message: M) { trace!(&LOGGER, "Sending: {message:?} to {:?}", self.subscribers()); for tx in self.subscribers() { @@ -41,16 +42,19 @@ pub trait BackendThread { } } } - fn start() -> Registrator { + fn start(self) -> Registrator + where + Self: Sized, + { let (registration_tx, registration_rx) = crossbeam_channel::unbounded::>(); - Self::init(registration_rx); + Self::init(self, registration_rx); registration_tx } } pub trait SimpleBackendThread: BackendThread { fn subscribers(&self) -> &Vec>; - fn init(registration_rx: Receiver>); + fn init(self, registration_rx: Receiver>); } impl> BackendThread for S { @@ -64,8 +68,8 @@ impl> BackendThread for S { SimpleBackendThread::subscribers(self) } - fn init(registration_rx: Receiver>) { - >::init(registration_rx) + fn init(self, registration_rx: Receiver>) { + >::init(self, registration_rx) } } @@ -95,17 +99,17 @@ pub enum ChannelType { } impl Backend { - pub fn start() -> Self { + pub fn start(config: &Config) -> Self { debug!(&LOGGER, "Starting Backend threads..."); - let hypr_registrator = HyprlandComms::start(); - let net_registrator = NetworkBackendHandle::start(); - let br_registrator = BrightnessManager::start(); - let stats_registrator = StatsBackend::start(); - let audio_registrator = AudioBackend::start(); - let media_registration = MediaBackend::start(); - let netbird_registrator = JsonSocketBackend::start(); - let idle_registrator = IdleDaemon::start(); - let control_registrator = ControlSocket::start(); + let hypr_registrator = HyprlandComms::new().start(); + let net_registrator = NetworkBackendHandle::new().start(); + let br_registrator = BrightnessManager::new().start(); + let stats_registrator = StatsBackend::new().start(); + let audio_registrator = AudioBackend::new().start(); + let media_registration = MediaBackend::new().start(); + let netbird_registrator = JsonSocketBackend::new().start(); + let idle_registrator = IdleDaemon::new(config.idle.clone()).start(); + let control_registrator = ControlSocket::new().start(); debug!(&LOGGER, "Started Backend threads"); Self { hypr_registrator, diff --git a/src/backend/audio.rs b/src/backend/audio.rs index 2363104..b7939e3 100644 --- a/src/backend/audio.rs +++ b/src/backend/audio.rs @@ -266,7 +266,16 @@ pub enum AudioMessage { pub struct AudioBackend { subscribers: Vec>, - command_tx: Sender, + command_tx: Option>, +} + +impl AudioBackend { + pub fn new() -> Self { + Self { + subscribers: vec![], + command_tx: None, + } + } } impl BackendThread> for AudioBackend { @@ -288,12 +297,10 @@ impl BackendThread> for AudioBackend { &self.subscribers } - fn init(registration_rx: Receiver>) { + fn init(mut self, registration_rx: Receiver>) { let (command_tx, command_rx) = crossbeam_channel::unbounded::(); - let instance = Arc::new(Mutex::new(Self { - subscribers: vec![], - command_tx, - })); + self.command_tx = Some(command_tx); + let instance = Arc::new(Mutex::new(self)); let (controller_tx, controller_rx) = crossbeam_channel::unbounded::(); let notify = Arc::new(EventFD::new(0, EfdFlags::EFD_CLOEXEC).unwrap()); thread::spawn({ @@ -381,7 +388,8 @@ impl BackendThread> for AudioBackend { .lock() .expect("Couldn't get lock") .command_tx - .clone(), + .clone() + .unwrap(), )) .is_ok() { @@ -460,13 +468,9 @@ impl BackendThread> for MediaBackend { &self.subscribers } - fn init(registration_rx: Receiver>) { + fn init(self, registration_rx: Receiver>) { let (tx, rx) = crossbeam_channel::unbounded::(); - let instance = Arc::new(Mutex::new(Self { - subscribers: vec![], - players: HashMap::new(), - active_player: None, - })); + let instance = Arc::new(Mutex::new(self)); thread::spawn({ let instance = instance.clone(); move || { @@ -545,6 +549,13 @@ impl BackendThread> for MediaBackend { } impl MediaBackend { + pub fn new() -> Self { + Self { + subscribers: vec![], + players: HashMap::new(), + active_player: None, + } + } async fn find_existing_players( connection: &zbus::Connection, instance: Arc>, diff --git a/src/backend/brightness_control.rs b/src/backend/brightness_control.rs index 4fe132e..1402199 100644 --- a/src/backend/brightness_control.rs +++ b/src/backend/brightness_control.rs @@ -143,12 +143,9 @@ impl BackendThread> for BrightnessM &self.subscribers } - fn init(registration_rx: Receiver>) { + fn init(self, registration_rx: Receiver>) { let (f2b_tx, f2b_rx) = crossbeam_channel::unbounded::(); - let instance = Arc::new(Mutex::new(Self { - display_cache: HashMap::new(), - subscribers: Vec::new(), - })); + let instance = Arc::new(Mutex::new(self)); let instance_clone = instance.clone(); thread::spawn(move || { while let Ok(tx) = registration_rx.recv() { @@ -246,6 +243,12 @@ enum Operation { } impl BrightnessManager { + pub fn new() -> Self { + Self { + subscribers: vec![], + display_cache: HashMap::new(), + } + } pub fn set_brightness(&mut self, connector: String, percent: u8) { if !self.display_cache.contains_key(&connector) { self.register_display(connector.clone()); diff --git a/src/backend/commands.rs b/src/backend/commands.rs index b092de6..57d62f9 100644 --- a/src/backend/commands.rs +++ b/src/backend/commands.rs @@ -12,15 +12,21 @@ pub struct ControlSocket { subscribers: Vec>, } +impl ControlSocket { + pub fn new() -> Self { + Self { + 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(), - })); + fn init(self, registration_rx: Receiver>) { + let instance = Arc::new(Mutex::new(self)); let instance_clone = instance.clone(); std::thread::spawn(move || { while let Ok(sender) = registration_rx.recv() { diff --git a/src/backend/gpu_utilization.rs b/src/backend/gpu_utilization.rs index baebb9b..144776e 100644 --- a/src/backend/gpu_utilization.rs +++ b/src/backend/gpu_utilization.rs @@ -30,7 +30,6 @@ struct PerfEventAttr { sig_data: u64, } -const PERF_TYPE_RAW: u32 = 4; const PERF_FORMAT_TOTAL_TIME_ENABLED: u64 = 1; const PERF_FORMAT_TOTAL_TIME_RUNNING: u64 = 2; @@ -95,8 +94,8 @@ impl I915Engine { fn open(name: &str) -> io::Result { let base = "/sys/bus/event_source/devices/i915"; - let pmu_type = read_number(&format!("{base}/type")).unwrap() as u32; - let config = read_config(&format!("{base}/events/{name}")).unwrap(); + let pmu_type = read_number(&format!("{base}/type"))? as u32; + let config = read_config(&format!("{base}/events/{name}"))?; let mut attr = PerfEventAttr::default(); attr.type_ = pmu_type; diff --git a/src/backend/idle.rs b/src/backend/idle.rs index df9830c..17f4c2b 100644 --- a/src/backend/idle.rs +++ b/src/backend/idle.rs @@ -1,5 +1,5 @@ use crate::backend::{BackendThread, Registrator}; -use crate::config::Config; +use crate::config::IdleConfig; use crate::{APP_NAME, LOGGER}; use crossbeam_channel::{Receiver, Sender}; use jmb_logger_rs::*; @@ -100,6 +100,7 @@ pub enum IdleMessage { #[derive(Debug)] pub struct IdleDaemon { subscribers: Vec>, + config: IdleConfig, } impl BackendThread> for IdleDaemon { @@ -121,12 +122,9 @@ impl BackendThread> for IdleDaemon { &self.subscribers } - fn init(registration_rx: Receiver>) { - let config = Config::load(); + fn init(self, registration_rx: Receiver>) { let (tx, rx) = crossbeam_channel::unbounded::(); - let instance = Arc::new(Mutex::new(Self { - subscribers: Vec::new(), - })); + let instance = Arc::new(Mutex::new(self)); std::thread::spawn({ let instance = instance.clone(); let tx = tx.clone(); @@ -146,9 +144,9 @@ impl BackendThread> for IdleDaemon { let (wl_tx, wl_rx) = watch::channel(false); let (dbus_tx, dbus_rx) = watch::channel(false); std::thread::spawn({ - let config = config.clone(); + let instance = instance.clone(); move || { - if let Err(err) = Self::start_idle_notifier(wl_tx, &config) { + if let Err(err) = Self::start_idle_notifier(wl_tx, instance) { error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}"); } } @@ -202,7 +200,7 @@ impl BackendThread> for IdleDaemon { let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await; match res { Ok((_, _)) => { - Self::run_idle_timer(instance, wl_rx, dbus_rx, &config).await; + Self::run_idle_timer(instance, wl_rx, dbus_rx).await; } Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"), } @@ -228,7 +226,7 @@ impl IdleStage { } } - fn timeout(self, cfg: &Config) -> Duration { + fn timeout(self, cfg: &IdleConfig) -> Duration { match self { Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()), Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()), @@ -238,11 +236,16 @@ impl IdleStage { } impl IdleDaemon { + pub fn new(config: IdleConfig) -> Self { + Self { + subscribers: vec![], + config, + } + } async fn run_idle_timer( instance: Arc>, mut wl_rx: watch::Receiver, mut dbus_rx: watch::Receiver, - config: &Config, ) { loop { if let Err(e) = wl_rx.changed().await { @@ -254,12 +257,13 @@ impl IdleDaemon { if wl_idle && !dbus_state { let mut stage = IdleStage::Dim; loop { + let timeout = stage.timeout(&instance.lock().unwrap().config); tokio::select! { - _ = sleep(stage.timeout(config)) => { + _ = sleep(timeout) => { match stage { IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed), - IdleStage::TurnOff => _ = Command::new("hyprlock").spawn(), - IdleStage::Sleep => _ = Command::new("systemctl").arg("sleep").spawn(), + IdleStage::TurnOff => _ = Command::new("bash").arg("-c").arg(format!("'{}'", instance.lock().unwrap().config.lock_command())).spawn(), + IdleStage::Sleep => _ = Command::new("bash").arg("-c").arg(format!("'{}'", instance.lock().unwrap().config.sleep_command())).spawn(), } stage = stage.next(); } @@ -286,7 +290,10 @@ impl IdleDaemon { } } - fn start_idle_notifier(tx: watch::Sender, config: &Config) -> Result<(), Box> { + fn start_idle_notifier( + tx: watch::Sender, + instance: Arc>, + ) -> Result<(), Box> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init::(&conn)?; let queue_handle = event_queue.handle(); @@ -298,7 +305,7 @@ impl IdleDaemon { let mut state = WlState { idle: None, tx }; let _notification = notifier.get_idle_notification( - (config.idle_timeout() * 1000) as u32, + (instance.lock().unwrap().config.idle_timeout() * 1000) as u32, &seat, &queue_handle, (), diff --git a/src/backend/json_socket.rs b/src/backend/json_socket.rs index 51563d2..88d77a5 100644 --- a/src/backend/json_socket.rs +++ b/src/backend/json_socket.rs @@ -37,6 +37,13 @@ impl State { } impl JsonSocketBackend { + pub fn new() -> Self { + Self { + subscribers: vec![], + state: initial_state_tailscale(), + socket_address: String::from("/var/run/netbird-http.sock"), + } + } async fn _watch(instance: Arc>) -> Result<(), Box> { let socket_address = instance .lock() @@ -197,14 +204,8 @@ impl BackendThread for JsonSocketBackend { &self.subscribers } - fn init(registration_rx: Receiver>) { - let state = initial_state_tailscale(); - - let instance = Arc::new(Mutex::new(Self { - subscribers: vec![], - state, - socket_address: String::from("/var/run/netbird-http.sock"), - })); + fn init(self, registration_rx: Receiver>) { + let instance = Arc::new(Mutex::new(self)); let instance_clone = instance.clone(); std::thread::spawn(move || { diff --git a/src/backend/network.rs b/src/backend/network.rs index d7e1c6b..a5d949d 100644 --- a/src/backend/network.rs +++ b/src/backend/network.rs @@ -112,7 +112,7 @@ pub enum NetworkState { #[derive(Debug, Clone)] pub struct NetworkBackendHandle { subscribers: Vec>, - command_tx: UnboundedSender, + command_tx: Option>, } struct EventDebouncer { @@ -140,12 +140,9 @@ impl BackendThread> for NetworkBac &self.subscribers } - fn init(registration_rx: Receiver>) { + fn init(mut self, registration_rx: Receiver>) { let (command_tx, mut command_rx) = tokio::sync::mpsc::unbounded_channel::(); - let instance = Self { - subscribers: vec![], - command_tx, - }; + self.command_tx = Some(command_tx); std::thread::spawn(move || { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() @@ -163,7 +160,7 @@ impl BackendThread> for NetworkBac std::thread::spawn({ let nm = nm.clone(); - let mut instance = instance.clone(); + let mut instance = self.clone(); move || { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() @@ -172,7 +169,7 @@ impl BackendThread> for NetworkBac runtime.block_on(async move { while let Ok(tx) = registration_rx.recv() { - if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() { + if tx.send(NetworkState::Init(instance.command_tx.clone().unwrap())).is_ok() { instance.subscribers.push(tx); let _ = instance.send_all_networks(&nm).await; let _ = instance.send_active_connection(&nm).await; @@ -205,7 +202,7 @@ impl BackendThread> for NetworkBac // Run the command without stopping event processing // while a potentially slow connection operation runs. let nm = nm.clone(); - let instance_clone = instance.clone(); + let instance_clone = self.clone(); tokio::spawn(async move { match command { @@ -241,7 +238,7 @@ impl BackendThread> for NetworkBac match event { Some(event_res) => { match event_res { - Ok(event) => instance.handle_event(&nm, event, &mut debouncer).await, + Ok(event) => self.handle_event(&nm, event, &mut debouncer).await, Err(err) => error!(&LOGGER, "Error: {err:?}"), } } @@ -250,7 +247,7 @@ impl BackendThread> for NetworkBac } _ = &mut debouncer.sleep, if debouncer.deadline.is_some() => { debouncer.deadline = None; - let _ = instance.send_all_networks(&nm).await; + let _ = self.send_all_networks(&nm).await; } } } @@ -260,6 +257,12 @@ impl BackendThread> for NetworkBac } impl NetworkBackendHandle { + pub fn new() -> NetworkBackendHandle { + Self { + subscribers: vec![], + command_tx: None, + } + } async fn connect(&self, nm: &NetworkManager, ssid: String, creds: WifiSecurity) { let previous_ssid = nm.current_ssid().await; let res = nm.connect(&ssid, None, creds).await; diff --git a/src/backend/perf_stats.rs b/src/backend/perf_stats.rs index e7b1d60..866494b 100644 --- a/src/backend/perf_stats.rs +++ b/src/backend/perf_stats.rs @@ -31,6 +31,14 @@ pub struct StatsBackend { subscribers: Vec>, } +impl StatsBackend { + pub fn new() -> Self { + Self { + subscribers: vec![], + } + } +} + impl BackendThread for StatsBackend { fn subscribe(registrator: Registrator) -> (Receiver, ()) { let (tx, rx) = crossbeam_channel::unbounded::(); @@ -42,10 +50,8 @@ impl BackendThread for StatsBackend { &self.subscribers } - fn init(registration_rx: Receiver>) { - let instance = Arc::new(Mutex::new(Self { - subscribers: Vec::new(), - })); + fn init(self, registration_rx: Receiver>) { + let instance = Arc::new(Mutex::new(self)); thread::spawn({ let instance = instance.clone(); move || { diff --git a/src/backend/window_manager_hypr.rs b/src/backend/window_manager_hypr.rs index 195934b..6e0c461 100644 --- a/src/backend/window_manager_hypr.rs +++ b/src/backend/window_manager_hypr.rs @@ -28,11 +28,9 @@ impl SimpleBackendThread for HyprlandComms { &self.subscribers } - fn init(registration_rx: Receiver>) { + fn init(self, registration_rx: Receiver>) { std::thread::spawn(move || { - let instance = Arc::new(Mutex::new(Self { - subscribers: vec![], - })); + let instance = Arc::new(Mutex::new(self)); let mut listener = EventListener::new(); let inst_clone = instance.clone(); @@ -153,6 +151,12 @@ impl SimpleBackendThread for HyprlandComms { } impl HyprlandComms { + pub fn new() -> Self { + Self { + subscribers: vec![], + } + } + fn monitor_id_from_workspace_id(id: WorkspaceId) -> Option { if let Ok(wss) = Workspaces::get() && let Some(ws) = wss.to_vec().iter().find(|ws| ws.id().unwrap() == id) diff --git a/src/config.rs b/src/config.rs index 394c4be..ad3a4e7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,23 +1,9 @@ use serde::Deserialize; use std::fs; -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Default)] pub(crate) struct Config { - idle_timeout: u64, - dim_timeout: u64, - lock_timeout: u64, - sleep_timeout: u64, -} - -impl Default for Config { - fn default() -> Self { - Self { - idle_timeout: 20, - dim_timeout: 60, - lock_timeout: 300, - sleep_timeout: 320, - } - } + pub idle: IdleConfig, } impl Config { @@ -28,7 +14,31 @@ impl Config { .flatten() .unwrap_or_default() } +} +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct IdleConfig { + idle_timeout: u64, + dim_timeout: u64, + lock_timeout: u64, + sleep_timeout: u64, + lock_command: String, + sleep_command: String, +} + +impl Default for IdleConfig { + fn default() -> Self { + Self { + idle_timeout: 20, + dim_timeout: 60, + lock_timeout: 300, + sleep_timeout: 320, + ..Default::default() + } + } +} + +impl IdleConfig { pub fn idle_timeout(&self) -> u64 { self.idle_timeout } @@ -56,4 +66,12 @@ impl Config { pub fn sleep_timeout_delta(&self) -> u64 { self.sleep_timeout - self.lock_timeout_delta() } + + pub fn lock_command(&self) -> &str { + &self.lock_command + } + + pub fn sleep_command(&self) -> &str { + &self.sleep_command + } } diff --git a/src/main.rs b/src/main.rs index 53ce003..9547b3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,13 +5,13 @@ mod css; mod ctl; mod ui; +use crate::config::Config; use glib::clone; use gtk::prelude::*; use gtk::{Application, CssProvider, gdk, gio}; use jmb_logger_rs::*; use notify::RecommendedWatcher; use std::cell::RefCell; -use std::process::Command; use std::rc::Rc; pub const LOGGER: Logger = Logger::create(Level::DEBUG); @@ -20,16 +20,14 @@ pub const APP_NAME: &str = "net.jmb19905.jmb-bar"; fn main() { info!(&LOGGER, "Starting jmb-bar"); - gio::resources_register_include!("templates.gresource").expect("Failed to register resources"); + let config = Config::load(); + let backend = Backend::start(&config); + gio::resources_register_include!("templates.gresource").expect("Failed to register resources"); let app = Application::builder().application_id(APP_NAME).build(); let watcher_slot: Rc>> = Rc::new(RefCell::new(None)); - let _ = Command::new("pkill hypridle").output(); // Kill existing hypridle processes - - let backend = Backend::start(); - app.connect_startup(move |_| { debug!(&LOGGER, "Connect startup"); let provider = CssProvider::new();