Add Ctl Socket, iterate on Idling daemon, update sysinfo crate, start on run menu

This commit is contained in:
jmb05
2026-10-30 16:49:21 +01:00
parent a6b601e3bd
commit 80b5cafb8c
17 changed files with 504 additions and 260 deletions

5
.gitignore vendored
View File

@@ -1,2 +1,7 @@
/target
/unused/
/jmb-bar
/perf.data
/qodana.yaml
/.idea/dictionaries/project.xml
/.idea/inspectionProfiles/Project_Default.xml

1
.idea/jmb-bar.iml generated
View File

@@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/unused" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

135
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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"

View File

@@ -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<M: Debug + Clone, D> {
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<BrightnessMessage>,
stats_registrator: Registrator<Stats>,
media_registrator: Registrator<MediaCommand>,
tailscale_registrator: Registrator<State>,
netbird_registrator: Registrator<State>,
idle_registrator: Registrator<IdleState>,
control_registrator: Registrator<ControlCommand>,
}
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<Receiver<NetworkState>>,
net_tx: UnboundedSender<NetworkCommand>,
net_tx: Option<UnboundedSender<NetworkCommand>>,
hypr_rx: Option<Receiver<WindowManagerEvent>>,
br_rx: Option<Receiver<BrightnessMessage>>,
br_tx: Sender<BrightnessMessage>,
br_tx: Option<Sender<BrightnessMessage>>,
stats_rx: Option<Receiver<Stats>>,
media_rx: Option<Receiver<MediaCommand>>,
media_tx: Sender<MediaCommand>,
tailscale_rx: Option<Receiver<State>>,
media_tx: Option<Sender<MediaCommand>>,
netbird_rx: Option<Receiver<State>>,
idle_rx: Option<Receiver<IdleState>>,
idle_tx: Sender<IdleMessage>,
idle_tx: Option<Sender<IdleMessage>>,
ctl_rx: Option<Receiver<ControlCommand>>,
}
impl BackendChannels {
@@ -150,7 +204,7 @@ impl BackendChannels {
self.stats_rx.take()
}
pub fn take_netbird_rx(&mut self) -> Option<Receiver<State>> {
self.tailscale_rx.take()
self.netbird_rx.take()
}
pub fn take_media_rx(&mut self) -> Option<Receiver<MediaCommand>> {
self.media_rx.take()
@@ -158,16 +212,19 @@ impl BackendChannels {
pub fn take_idle_rx(&mut self) -> Option<Receiver<IdleState>> {
self.idle_rx.take()
}
pub fn net_tx(&self) -> UnboundedSender<NetworkCommand> {
pub fn take_ctl_rx(&mut self) -> Option<Receiver<ControlCommand>> {
self.ctl_rx.take()
}
pub fn net_tx(&self) -> Option<UnboundedSender<NetworkCommand>> {
self.net_tx.clone()
}
pub fn br_tx(&self) -> Sender<BrightnessMessage> {
pub fn br_tx(&self) -> Option<Sender<BrightnessMessage>> {
self.br_tx.clone()
}
pub fn media_tx(&self) -> Sender<MediaCommand> {
pub fn media_tx(&self) -> Option<Sender<MediaCommand>> {
self.media_tx.clone()
}
pub fn idle_tx(&self) -> Sender<IdleMessage> {
pub fn idle_tx(&self) -> Option<Sender<IdleMessage>> {
self.idle_tx.clone()
}
}

View File

@@ -215,14 +215,19 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> 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));
}
}

62
src/backend/commands.rs Normal file
View File

@@ -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<Sender<ControlCommand>>,
}
impl SimpleBackendThread<ControlCommand> for ControlSocket {
fn subscribers(&self) -> &Vec<Sender<ControlCommand>> {
&self.subscribers
}
fn init(registration_rx: Receiver<Sender<ControlCommand>>) {
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);
}
}
}
});
}
}

View File

@@ -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<bool>,
tx: watch::Sender<IdleStateInner>,
tx: watch::Sender<bool>,
}
delegate_noop!(WlState: ignore WlSeat);
@@ -53,14 +53,18 @@ impl Dispatch<ExtIdleNotificationV1, ()> for WlState {
_: &QueueHandle<Self>,
) {
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<IdleMessage>),
@@ -107,20 +97,6 @@ pub enum IdleState {
Active,
}
impl From<IdleStateInner> 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<IdleState, Sender<IdleMessage>> 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<IdleState, Sender<IdleMessage>> 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<IdleState, Sender<IdleMessage>> 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<Mutex<Self>>,
mut state_rx: watch::Receiver<IdleStateInner>,
timeout: Duration,
_instance: Arc<Mutex<Self>>,
mut wl_rx: watch::Receiver<bool>,
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<IdleStateInner>) -> Result<(), Box<dyn Error>> {
fn start_idle_notifier(tx: watch::Sender<bool>) -> Result<(), Box<dyn Error>> {
let conn = Connection::connect_to_env()?;
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
let queue_handle = event_queue.handle();
@@ -281,7 +288,7 @@ impl IdleDaemon {
}
async fn start_dbus(
tx: watch::Sender<IdleStateInner>,
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<u32, Inhibitor>,
tx: watch::Sender<IdleStateInner>,
tx: watch::Sender<(bool, bool)>,
}
impl ScreenSaver {
fn new(tx: watch::Sender<IdleStateInner>) -> 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(())
}

View File

@@ -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<u8>,
pub ram: Option<u8>,
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<Sender<Stats>>,
system: System,
disks: Disks,
system: Option<System>,
disks: Option<Disks>,
}
impl BackendThread<Stats, ()> for StatsBackend {
@@ -47,8 +47,8 @@ impl BackendThread<Stats, ()> 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<Stats, ()> 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 {

51
src/ctl.rs Normal file
View File

@@ -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<Self> {
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<String> = 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,
};
}

View File

@@ -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();
}

View File

@@ -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<Mutex<HashMap<u32, ApplicationWindow>>> = 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,

View File

@@ -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,

77
src/ui/run_menu.rs Normal file
View File

@@ -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();
}
}

View File

@@ -62,20 +62,11 @@ pub fn build(
String::new()
};
let monitor_valid = !monitor.is_empty();
println!(
"{:?}",
monitors
.item(monitor_idx)
.unwrap()
.downcast::<gdk::Monitor>()
.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));

5
style/run_menu.scss Normal file
View File

@@ -0,0 +1,5 @@
@use "colors" as *;
.run-menu {
background: $background;
}

View File

@@ -1,3 +1,4 @@
@use "widgets";
@use "bar";
@use "action_centre";
@use "run_menu";