#!/usr/bin/env rust-script //! //! ```cargo //! [dependencies] //! serde = { version = "1.0", features = ["derive"] } //! serde_json = "1.0" //! anyhow = "1.0" //! handlebars = "3" //! log = "0.4" //! env_logger = "0.10" //! tokio = { version = "1", features = ["full"] } //! ``` use anyhow::Result; use handlebars::Handlebars; use serde::Deserialize; use std::collections::HashMap; use std::process::Command; use std::{fs, io}; use log::{info, debug}; #[derive(Deserialize, Debug)] struct Monitor { id: u32, name: String, width: u32, height: u32, } impl Monitor { fn list() -> Result> { let output = Command::new("hyprctl").arg("monitors").arg("-j").output()?; assert!(output.status.success()); let monitors_json = std::str::from_utf8(&output.stdout)?; Ok(serde_json::from_str(monitors_json)?) } fn launch_bar(&self, wl_display: &str, home: &str) -> Result<()> { let bar_name = format!("bar-{wl_display}-{}", self.id); let dir = format!("/tmp/vbar/{wl_display}/{}", self.id); info!("Launching bar {bar_name}"); match fs::create_dir_all(&dir) { Ok(()) => {} Err(e) if e.kind() == io::ErrorKind::AlreadyExists => { info!("Directory {dir} already exists. Recreating..."); let output = Command::new("rm").arg("-rf").arg(&dir).output()?; assert!(output.status.success()); fs::create_dir_all(&dir)?; } Err(e) => return Err(e.into()), } self.generate_bar_config( &bar_name, &home, &dir, &format!("{home}/.config/eww/templates/bar"), &dir, )?; Command::new("eww") .arg("-c") .arg(&dir) .arg("open") .arg("--restart") .arg(&bar_name) .spawn()?; info!("Successfuly launched bar {bar_name}"); Ok(()) } fn generate_bar_config( &self, bar_name: &str, home: &str, base_dir: &str, source_dir: &str, dest_dir: &str, ) -> Result<()> { for entry in fs::read_dir(source_dir)? { let entry = entry?; let path = entry.path(); let relpath = path.strip_prefix(&source_dir)?.to_str().unwrap(); if path.is_dir() { fs::create_dir_all(&format!("{dest_dir}/{relpath}"))?; self.generate_bar_config( &bar_name, &home, &base_dir, &path.to_str().unwrap(), &format!("{dest_dir}/{relpath}"), )?; } else if path.is_file() { let data = fs::read_to_string(&path)?; let output = self.template(&data, &bar_name, &home, &base_dir)?; fs::write(format!("{dest_dir}/{relpath}"), output)?; } } Ok(()) } fn template(&self, source: &str, bar_name: &str, home: &str, base_dir: &str) -> Result { let height: u32 = (self.height as f32 * 0.02) as u32; let id_str = self.id.to_string(); let height_str = height.to_string(); let values: HashMap<&str, &str> = HashMap::from([ ("monitor", id_str.as_str()), ("monitor_name", self.name.as_str()), ("bar_name", bar_name), ("home", home), ("base_dir", base_dir), ("height", height_str.as_str()), ]); let mut handlebars = Handlebars::new(); handlebars.register_template_string("tpl", source)?; Ok(handlebars.render("tpl", &values)?) } } #[tokio::main] async fn main() -> Result<()> { env_logger::init(); info!("Starting..."); let wl_display = std::env::var_os("WAYLAND_DISPLAY") .unwrap() .into_string() .unwrap(); let home = std::env::var_os("HOME").unwrap().into_string().unwrap(); let monitors = Monitor::list()?; info!("Received monitor list"); debug!("{:?}", monitors); let mut handles = Vec::new(); for monitor in monitors { let home = home.clone(); let wl_display = wl_display.clone(); let handle = tokio::spawn(async move { monitor.launch_bar(&wl_display, &home).unwrap(); }); handles.push(handle); } for handle in handles { handle.await?; } Ok(()) }