Add scss colors into rust via build-script, flesh out math evaluation in launcher, Restructure some code

This commit is contained in:
jmb05
2026-11-02 19:31:21 +01:00
parent 11fb03427c
commit c3ad6f38cf
15 changed files with 167 additions and 114 deletions

1
Cargo.lock generated
View File

@@ -1598,6 +1598,7 @@ dependencies = [
"nmrs",
"notify",
"nucleo-matcher",
"regex",
"rustsni",
"serde",
"serde_json",

View File

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

View File

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

View File

@@ -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<ControlCommand>,
}
#[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)
}
}

View File

@@ -516,33 +516,35 @@ impl BackendThread<MediaMessage, Sender<MediaMessage>> 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)
{
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 {
instance_guard.active_player_play(&connection).await;
MediaBackend::player_play(name, &connection).await;
}
}
MediaMessage::PlayerPause => {
if let PlayerStatus::Playing(_) = status {
instance_guard.active_player_pause(&connection).await;
MediaBackend::player_pause(name, &connection).await;
instance.lock().unwrap().update_active_player();
}
}
MediaMessage::PlayerStop => {
if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) =
status
{
instance_guard.active_player_stop(&connection).await;
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<String>) {

View File

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

View File

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

View File

@@ -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<Self, Self::Err> {
match s {
"menu" => Ok(ControlCommand::OpenRunMenu),
other => Err(format!("Unknown command: {other}")),
}
}
}
fn main() {
info!(&CTL_LOGGER, "Hello, this is mission control");

View File

@@ -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::<Config>(&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,

31
src/lib.rs Normal file
View File

@@ -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<Self, Self::Err> {
match s {
"menu" => Ok(ControlCommand::OpenRunMenu),
other => Err(format!("Unknown command: {other}")),
}
}
}

View File

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

1
src/theme.rs Normal file
View File

@@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/theme.rs"));

View File

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

View File

@@ -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));
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::<gtk::Button>()
{
if let Some(child) = content_box.first_child() {
if let Ok(button) = child.clone().downcast::<gtk::Button>() {
button.activate();
} else if let Ok(label) = child.downcast::<gtk::Label>() {
display.clipboard().set_text(&label.text());
label.set_markup(&format!(
"{}<span foreground=\"{}\"><i> - Copied</i></span>",
label.text(),
theme::LIGHTGRAY
));
}
}
}
));

View File

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