Compare commits

..

2 commits

Author SHA1 Message Date
1b6c9e46be
Add argument parsing and default config file. 2023-10-15 01:18:00 +02:00
b76d325d60
Add logging. 2023-10-15 00:33:55 +02:00
3 changed files with 72 additions and 11 deletions

View file

@ -8,9 +8,13 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.75" anyhow = "1.0.75"
byteorder = "1.5.0" byteorder = "1.5.0"
clap = { version = "4.4.6", features = ["derive"] }
env_logger = "0.10.0"
futures = "0.3.28" futures = "0.3.28"
log = "0.4.20"
rust_lisp = { git = "https://github.com/brundonsmith/rust_lisp.git", branch = "arc-feature-addition", features = ["arc"] } rust_lisp = { git = "https://github.com/brundonsmith/rust_lisp.git", branch = "arc-feature-addition", features = ["arc"] }
serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] } serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] }
serde_json = "1.0.107" serde_json = "1.0.107"
serde_yaml = "0.9.25" serde_yaml = "0.9.25"
tokio = { version = "1.33.0", features = ["full"] } tokio = { version = "1.33.0", features = ["full"] }
xdg = "2.5.2"

View file

@ -1,3 +1,4 @@
use std::fmt::{Display, Formatter};
use serde::{Serialize, Deserialize, Serializer, Deserializer}; use serde::{Serialize, Deserialize, Serializer, Deserializer};
use rust_lisp::model::Value as RValue; use rust_lisp::model::Value as RValue;
@ -18,6 +19,18 @@ impl Into<Vec<RValue>> for Value {
} }
} }
impl Display for Value {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
let mut s = String::new();
s.push_str("(begin\n");
for i in &self.0 {
s.push_str(&format!("{}\n", i));
}
s.push_str(")");
write!(f, "{}", &s)
}
}
impl<'de> Deserialize<'de> for Value { impl<'de> Deserialize<'de> for Value {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
@ -29,7 +42,6 @@ impl<'de> Deserialize<'de> for Value {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Program { pub struct Program {

View file

@ -1,6 +1,10 @@
use anyhow::Result; use anyhow::Result;
use tokio::time::{timeout, Duration}; use tokio::time::{timeout, Duration};
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use clap::Parser;
use log::{info, debug, warn};
use std::path::PathBuf;
use std::str::FromStr;
mod config; mod config;
mod i3ipc; mod i3ipc;
@ -9,37 +13,69 @@ mod lisp;
use config::Config; use config::Config;
use i3ipc::{Connection, MessageType}; use i3ipc::{Connection, MessageType};
#[derive(Debug, Clone)]
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
}
impl Args {
fn finish(&mut self) {
// TODO maybe return separate type
if self.config.is_none() {
self.config = Some(xdg::BaseDirectories::with_prefix("i3toolwait").unwrap().get_config_file("config.yaml"));
}
}
}
fn new_window_cb( fn new_window_cb(
b: MessageType, b: MessageType,
c: serde_json::Value, c: serde_json::Value,
config: &Config, config: &Config,
args: &Args,
) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec<u8>)>> { ) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec<u8>)>> {
let config_ = config.clone(); let config_ = config.clone();
Box::pin(async move { Box::pin(async move {
debug!("Received window event: {}", &c);
for p in config_.programs.iter() { for p in config_.programs.iter() {
debug!("Evaluating program: {}", &p.match_);
let e = lisp::env(&c); let e = lisp::env(&c);
let init: Vec<rust_lisp::model::Value> = config_.init.clone().into(); let init: Vec<rust_lisp::model::Value> = config_.init.clone().into();
let prog: Vec<rust_lisp::model::Value> = p.match_.clone().into(); let prog: Vec<rust_lisp::model::Value> = p.match_.clone().into();
let m = init.into_iter().chain(prog.into_iter()); let m = init.into_iter().chain(prog.into_iter());
let result = rust_lisp::interpreter::eval_block(rust_lisp::model::reference::new(e), m); let result = rust_lisp::interpreter::eval_block(rust_lisp::model::reference::new(e), m);
println!("{:?}", result);
if let Ok(rust_lisp::model::Value::True) = result { if let Ok(rust_lisp::model::Value::True) = result {
debug!("Match found");
return vec![(MessageType::Command, p.cmd.clone().into_bytes())]; return vec![(MessageType::Command, p.cmd.clone().into_bytes())];
} }
} }
debug!("No match found");
Vec::new() Vec::new()
}) })
} }
async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> { async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> {
let resp = connection.communicate(&MessageType::Version, b"").await?; let (_, resp) = connection.communicate(&MessageType::Version, b"").await?;
println!("{:?}", resp); info!("i3 version is {}", resp.get("human_readable").unwrap());
for p in config.programs.iter() { for p in config.programs.iter() {
if let Some(r) = &p.run { if let Some(r) = &p.run {
let (message_type, response) = connection.communicate(&MessageType::Command, r.as_bytes()).await?; let (_, responses) = connection.communicate(&MessageType::Command, r.as_bytes()).await?;
println!("{:?}", (message_type, response)); match responses {
serde_json::Value::Array(responses) => {
for response in responses {
if let serde_json::Value::Bool(v) = response.get("success").unwrap() {
if !v {
warn!("Failed to run command {}: {}", r, response);
}
}
}
},
_ => panic!("invalid response"),
};
} }
} }
Ok(()) Ok(())
@ -47,16 +83,25 @@ async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(),
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; env_logger::init_from_env(env_logger::Env::new().filter("I3TOOLWAIT_LOG").write_style("I3TOOLWAIT_LOG_STYLE"));
let mut sub_connection = connection.clone();
let mut args = Args::parse();
args.finish();
let args = std::sync::Arc::new(args);
let mut config = String::new(); let mut config = String::new();
tokio::fs::File::open("/home/redxef/CODE/i3toolwait/i3_autostart.yaml").await?.read_to_string(&mut config).await?; if args.config.as_ref().unwrap() == &PathBuf::from_str("-").unwrap() {
tokio::io::stdin().read_to_string(&mut config).await?;
} else {
tokio::fs::File::open(args.config.as_ref().unwrap()).await?.read_to_string(&mut config).await?;
}
let config: Config = serde_yaml::from_str(&config)?; let config: Config = serde_yaml::from_str(&config)?;
let config = std::sync::Arc::new(config); let config = std::sync::Arc::new(config);
let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?;
let mut sub_connection = connection.clone();
let cb_config = config.clone(); let cb_config = config.clone();
let cb = move |a, b| {new_window_cb(a, b, &cb_config)}; let cb_args = args.clone();
let cb = move |a, b| {new_window_cb(a, b, &cb_config, &cb_args)};
sub_connection sub_connection
.subscribe(&[MessageType::SubWindow], &cb) .subscribe(&[MessageType::SubWindow], &cb)
.await?; .await?;