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