From c3ad6f38cf78b86a8649b6afa601e0cd6530eb93 Mon Sep 17 00:00:00 2001 From: jmb05 Date: Mon, 2 Nov 2026 19:31:21 +0100 Subject: [PATCH] Add scss colors into rust via build-script, flesh out math evaluation in launcher, Restructure some code --- Cargo.lock | 1 + Cargo.toml | 4 +- build.rs | 28 +++++++++ src/backend.rs | 13 ++-- src/backend/audio.rs | 97 ++++++++++++----------------- src/backend/commands.rs | 2 +- src/backend/idle.rs | 2 +- src/{ => bin}/ctl.rs | 27 +------- src/config.rs | 6 +- src/lib.rs | 31 +++++++++ src/main.rs | 4 +- src/theme.rs | 1 + src/ui.rs | 8 +-- src/ui/run_menu.rs | 56 +++++++++++++---- style/{colors.scss => _colors.scss} | 1 + 15 files changed, 167 insertions(+), 114 deletions(-) rename src/{ => bin}/ctl.rs (57%) create mode 100644 src/lib.rs create mode 100644 src/theme.rs rename style/{colors.scss => _colors.scss} (81%) diff --git a/Cargo.lock b/Cargo.lock index 934c393..dbf3ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1598,6 +1598,7 @@ dependencies = [ "nmrs", "notify", "nucleo-matcher", + "regex", "rustsni", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index f12fe6d..b0b056d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ path = "src/main.rs" [[bin]] name = "jmbctl" -path = "src/ctl.rs" +path = "src/bin/ctl.rs" [dependencies] grass = "0.13.4" @@ -49,3 +49,5 @@ toml = "1.1.8" [build-dependencies] glib-build-tools = "0.22.8" +grass = "0.13.4" +regex = "1.13.1" diff --git a/build.rs b/build.rs index e92a8cd..0ce171f 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,35 @@ +use regex::Regex; +use std::path::PathBuf; +use std::{env, fs}; + fn main() { glib_build_tools::compile_resources( &["resources"], "resources/resources.gresource.xml", "templates.gresource", ); + + let colors_path = "style/_colors.scss"; + + println!("cargo:rerun-if-changed={colors_path}"); + + let scss = fs::read_to_string(colors_path).expect("Failed to read SCSS colors"); + + let regex = + Regex::new(r"(?m)^\s*\$([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(#[0-9a-fA-F]{3,8})\s*;").unwrap(); + + let mut generated = String::from("// Generated by build.rs. Do not edit\n\n"); + + for captures in regex.captures_iter(&scss) { + let name = captures.get(1).unwrap().as_str(); + let value = captures.get(2).unwrap().as_str(); + + let constant_name = name.replace('-', "_").to_uppercase(); + + generated.push_str(&format!("pub const {constant_name}: &str = \"{value}\";\n")); + } + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + fs::write(out_dir.join("theme.rs"), generated).expect("Failed to generate theme.rs"); } diff --git a/src/backend.rs b/src/backend.rs index 49b3b34..9a22f5b 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -17,9 +17,9 @@ use crate::backend::json_socket::{JsonSocketBackend, State}; use crate::backend::network::{NetworkBackendHandle, NetworkCommand, NetworkState}; use crate::backend::perf_stats::{Stats, StatsBackend}; use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent}; -use crate::config::Config; -use crate::ctl::ControlCommand; use crossbeam_channel::{Receiver, Sender}; +use jmb_bar::ControlCommand; +use jmb_bar::config::Config; use jmb_logger_rs::*; use std::fmt::Debug; use tokio::sync::mpsc::UnboundedSender; @@ -86,6 +86,7 @@ pub struct Backend { control_registrator: Registrator, } +#[derive(PartialEq)] pub enum ChannelType { Net, Hypr, @@ -174,8 +175,8 @@ impl Backend { channels } - pub fn subscribe_all(&self) -> BackendChannels { - self.subscribe(&[ + pub fn subscribe_except(&self, channel_types: &[ChannelType]) -> BackendChannels { + let mut types = vec![ ChannelType::Net, ChannelType::Idle, ChannelType::Netbird, @@ -185,7 +186,9 @@ impl Backend { ChannelType::Audio, ChannelType::Media, ChannelType::Hypr, - ]) + ]; + types.retain(|i| !channel_types.contains(i)); + self.subscribe(&types) } } diff --git a/src/backend/audio.rs b/src/backend/audio.rs index 167aa66..69887da 100644 --- a/src/backend/audio.rs +++ b/src/backend/audio.rs @@ -516,30 +516,32 @@ impl BackendThread> for MediaBackend { return; }; while let Ok(msg) = rx.recv() { - let mut instance_guard = instance.lock().unwrap(); - if let Some(name) = &instance_guard.active_player - && let Some(status) = instance_guard.players.get(name) - { - match msg { - MediaMessage::PlayerPlay => { - if let PlayerStatus::Paused(_) = status { - instance_guard.active_player_play(&connection).await; - } + let Some(name) = &instance.lock().unwrap().active_player.clone() else { + continue; + }; + let Some(status) = instance.lock().unwrap().players.get(name).cloned() + else { + continue; + }; + match msg { + MediaMessage::PlayerPlay => { + if let PlayerStatus::Paused(_) = status { + MediaBackend::player_play(name, &connection).await; } - MediaMessage::PlayerPause => { - if let PlayerStatus::Playing(_) = status { - instance_guard.active_player_pause(&connection).await; - } - } - MediaMessage::PlayerStop => { - if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) = - status - { - instance_guard.active_player_stop(&connection).await; - } - } - _ => {} } + MediaMessage::PlayerPause => { + if let PlayerStatus::Playing(_) = status { + MediaBackend::player_pause(name, &connection).await; + instance.lock().unwrap().update_active_player(); + } + } + MediaMessage::PlayerStop => { + if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) = status { + MediaBackend::player_stop(name, &connection).await; + instance.lock().unwrap().select_active_player(None); + } + } + _ => {} } } } @@ -756,54 +758,35 @@ impl MediaBackend { } } - async fn active_player_play(&self, connection: &zbus::Connection) { - if let Some(player_name) = &self.active_player - && let Ok(proxy) = zbus::Proxy::new( - connection, - player_name.as_str(), - MPRIS_PATH, - MPRIS_PLAYER_INTERFACE, - ) - .await + async fn player_play(name: &str, connection: &zbus::Connection) { + if let Ok(proxy) = + zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await && let Err(e) = proxy.call_method("Play", &()).await { - warn!( - &LOGGER, - "Error starting play for player: {player_name}, {e}" - ); + warn!(&LOGGER, "Error starting play for player: {name}, {e}"); } } - async fn active_player_pause(&mut self, connection: &zbus::Connection) { - if let Some(player_name) = &self.active_player - && let Ok(proxy) = zbus::Proxy::new( - connection, - player_name.as_str(), - MPRIS_PATH, - MPRIS_PLAYER_INTERFACE, - ) - .await + async fn player_pause(name: &str, connection: &zbus::Connection) { + if let Ok(proxy) = + zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await && let Err(e) = proxy.call_method("Pause", &()).await { - warn!(&LOGGER, "Error pausing player: {player_name}, {e}"); - self.select_active_player(self.active_player.clone()); + warn!(&LOGGER, "Error pausing player: {name}, {e}"); } } - async fn active_player_stop(&mut self, connection: &zbus::Connection) { - if let Some(player_name) = &self.active_player - && let Ok(proxy) = zbus::Proxy::new( - connection, - player_name.as_str(), - MPRIS_PATH, - MPRIS_PLAYER_INTERFACE, - ) - .await + async fn player_stop(name: &str, connection: &zbus::Connection) { + if let Ok(proxy) = + zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await && let Err(e) = proxy.call_method("Stop", &()).await { - warn!(&LOGGER, "Error stopping player: {player_name}, {e}"); + warn!(&LOGGER, "Error stopping player: {name}, {e}"); } - self.select_active_player(None); + } + + fn update_active_player(&mut self) { + self.select_active_player(self.active_player.clone()); } fn select_active_player(&mut self, changed_player: Option) { diff --git a/src/backend/commands.rs b/src/backend/commands.rs index acea831..b7696cd 100644 --- a/src/backend/commands.rs +++ b/src/backend/commands.rs @@ -1,7 +1,7 @@ use crate::LOGGER; use crate::backend::{BackendThread, SimpleBackendThread}; -use crate::ctl::{ControlCommand, SOCKET_PATH}; use crossbeam_channel::{Receiver, Sender}; +use jmb_bar::{ControlCommand, SOCKET_PATH}; use jmb_logger_rs::*; use std::io::Read; use std::net::Shutdown; diff --git a/src/backend/idle.rs b/src/backend/idle.rs index 17f4c2b..4f06924 100644 --- a/src/backend/idle.rs +++ b/src/backend/idle.rs @@ -1,7 +1,7 @@ use crate::backend::{BackendThread, Registrator}; -use crate::config::IdleConfig; use crate::{APP_NAME, LOGGER}; use crossbeam_channel::{Receiver, Sender}; +use jmb_bar::config::IdleConfig; use jmb_logger_rs::*; use std::collections::HashMap; use std::error::Error; diff --git a/src/ctl.rs b/src/bin/ctl.rs similarity index 57% rename from src/ctl.rs rename to src/bin/ctl.rs index 0d213a5..900a4fa 100644 --- a/src/ctl.rs +++ b/src/bin/ctl.rs @@ -1,36 +1,11 @@ +use jmb_bar::{ControlCommand, SOCKET_PATH}; use jmb_logger_rs::*; use std::env; -use std::fmt::{Display, Formatter}; use std::io::Write; use std::os::unix::net::UnixStream; use std::str::FromStr; 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 Display for ControlCommand { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ControlCommand::OpenRunMenu => f.write_str("menu"), - } - } -} - -impl FromStr for ControlCommand { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "menu" => Ok(ControlCommand::OpenRunMenu), - other => Err(format!("Unknown command: {other}")), - } - } -} fn main() { info!(&CTL_LOGGER, "Hello, this is mission control"); diff --git a/src/config.rs b/src/config.rs index d36a13c..3c18090 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,13 +3,13 @@ use std::default::Default; use std::fs; #[derive(Deserialize, Debug, Clone, Default)] -pub(crate) struct Config { +pub struct Config { pub idle: IdleConfig, } impl Config { pub fn load() -> Config { - fs::read_to_string("jmb-bar.conf") + fs::read_to_string("../jmb-bar.conf") .ok() .and_then(|s| toml::from_str::(&s).ok()) .unwrap_or_default() @@ -17,7 +17,7 @@ impl Config { } #[derive(Deserialize, Debug, Clone)] -pub(crate) struct IdleConfig { +pub struct IdleConfig { idle_timeout: u64, dim_timeout: u64, lock_timeout: u64, diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c864fef --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,31 @@ +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +pub mod config; +pub mod theme; + +pub const SOCKET_PATH: &str = "/run/user/1000/jmb-bar.sock"; + +#[derive(Clone, Debug)] +pub enum ControlCommand { + OpenRunMenu, +} + +impl Display for ControlCommand { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ControlCommand::OpenRunMenu => f.write_str("menu"), + } + } +} + +impl FromStr for ControlCommand { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "menu" => Ok(ControlCommand::OpenRunMenu), + other => Err(format!("Unknown command: {other}")), + } + } +} diff --git a/src/main.rs b/src/main.rs index 9547b3c..a44ae52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,12 @@ use crate::backend::Backend; mod backend; -mod config; mod css; -mod ctl; mod ui; -use crate::config::Config; use glib::clone; use gtk::prelude::*; use gtk::{Application, CssProvider, gdk, gio}; +use jmb_bar::config::Config; use jmb_logger_rs::*; use notify::RecommendedWatcher; use std::cell::RefCell; diff --git a/src/theme.rs b/src/theme.rs new file mode 100644 index 0000000..da1223f --- /dev/null +++ b/src/theme.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/theme.rs")); diff --git a/src/ui.rs b/src/ui.rs index d2a8c1a..08b87a8 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -2,7 +2,6 @@ use crate::LOGGER; use crate::backend::audio::MediaMessage; use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent}; use crate::backend::{Backend, BackendChannels, ChannelType}; -use crate::ctl::ControlCommand; use crate::ui::action_centre::ActionCentre; use crate::ui::run_menu::RunMenu; use glib::ControlFlow; @@ -11,6 +10,7 @@ use gtk::gdk::Display; use gtk::prelude::{BoxExt, ButtonExt, DisplayExt, GtkWindowExt, ListModelExt, WidgetExt}; use gtk::{Align, Application, ApplicationWindow, Button, Orientation, gdk}; use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell}; +use jmb_bar::ControlCommand; use jmb_logger_rs::*; use std::collections::HashMap; use std::rc::Rc; @@ -29,11 +29,11 @@ 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_all(); + let ac_channels = backend.subscribe(&[ChannelType::Net]); 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_all(); + let channels = backend.subscribe_except(&[ChannelType::Control]); bars.lock().expect("Couldn't get active bars").insert( monitor, create_and_show_bar( @@ -58,7 +58,7 @@ 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_clone.subscribe_all(); + let channels = backend_clone.subscribe_except(&[ChannelType::Control]); let bars = bars.clone(); glib::idle_add_local_once(move || { let display = Display::default().expect("Couldn't find GDK Display"); diff --git a/src/ui/run_menu.rs b/src/ui/run_menu.rs index 5e98e91..b5ed2f9 100644 --- a/src/ui/run_menu.rs +++ b/src/ui/run_menu.rs @@ -10,6 +10,7 @@ use gtk::prelude::{ }; use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk}; use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell}; +use jmb_bar::theme; use jmb_logger_rs::*; use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern}; use nucleo_matcher::{Config, Matcher}; @@ -92,20 +93,42 @@ impl RunMenu { entry.connect_changed(clone!( #[strong] content_box, - move |e| { + move |entry| { while let Some(child) = content_box.first_child() { content_box.remove(&child); } - if e.text_length() > 0 { - let text = e.text(); + if entry.text_length() > 0 { + let text = entry.text(); if let Some(eval) = text.strip_prefix("=") { - let label = gtk::Label::new(Some(eval)); - label.add_css_class("exec-button"); - label.set_halign(Align::Start); - label.set_hexpand(true); - content_box.append(&label); + let new_eval = eval.replace("**", "^"); + if new_eval != eval { + let cursor = entry.position() as usize; + let removed_before_cursor = text[..cursor].matches("**").count(); + let new_cursor = cursor - removed_before_cursor; + + glib::idle_add_local_once(clone!( + #[weak] + entry, + move || { + entry.set_text(&format!("={new_eval}")); + entry.set_position(new_cursor as i32); + } + )); + } + if let Ok(result) = meval::eval_str(eval) { + let label = gtk::Label::new(Some(&format!("{}", result))); + label.add_css_class("exec-button"); + label.set_halign(Align::Start); + label.set_hexpand(true); + content_box.append(&label); + instance_clone.window.set_default_size(500, -1); + } } else { - instance_clone.list_matching(e, &content_box, &mut matcher_rc.borrow_mut()); + instance_clone.list_matching( + entry, + &content_box, + &mut matcher_rc.borrow_mut(), + ); instance_clone.window.set_default_size(500, -1); } } @@ -128,10 +151,17 @@ impl RunMenu { #[strong] content_box, move |_| { - if let Some(child) = content_box.first_child() - && let Ok(button) = child.downcast::() - { - button.activate(); + if let Some(child) = content_box.first_child() { + if let Ok(button) = child.clone().downcast::() { + button.activate(); + } else if let Ok(label) = child.downcast::() { + display.clipboard().set_text(&label.text()); + label.set_markup(&format!( + "{} - Copied", + label.text(), + theme::LIGHTGRAY + )); + } } } )); diff --git a/style/colors.scss b/style/_colors.scss similarity index 81% rename from style/colors.scss rename to style/_colors.scss index 76e6840..87d93c9 100644 --- a/style/colors.scss +++ b/style/_colors.scss @@ -1,3 +1,4 @@ +// Changing colors here will not apply to the constants in theme.rs until the app is recompiled $background: #232320; $background1: #3c3836;