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

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));