From 81790d43088f19f5d3335c597b27f145ca77c6b6 Mon Sep 17 00:00:00 2001 From: redxef Date: Sat, 14 Oct 2023 00:26:34 +0200 Subject: [PATCH 1/9] Initial rust rewrite commit, still wip. --- .gitignore | 1 + Cargo.toml | 15 ++++ src/i3ipc.rs | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 48 +++++++++++ 4 files changed, 290 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/i3ipc.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f71e683 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "i3toolwait" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +byteorder = "1.5.0" +futures = "0.3.28" +rust_lisp = "0.18.0" +serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] } +serde_json = "1.0.107" +tokio = { version = "1.33.0", features = ["full"] } diff --git a/src/i3ipc.rs b/src/i3ipc.rs new file mode 100644 index 0000000..4a6cd7c --- /dev/null +++ b/src/i3ipc.rs @@ -0,0 +1,226 @@ +use anyhow::Result; +use futures::future::BoxFuture; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::pin::Pin; +use std::str::FromStr; +use std::sync::Arc; +use tokio::io::{AsyncReadExt, AsyncWriteExt, BufStream}; +use tokio::net::UnixStream; + +pub async fn get_socket_path() -> Result { + if let Ok(p) = std::env::var("I3SOCK") { + return Ok(std::path::PathBuf::from_str(&p).unwrap()); + } + if let Ok(p) = std::env::var("SWAYSOCK") { + return Ok(std::path::PathBuf::from_str(&p).unwrap()); + } + + for command_name in ["i3", "sway"] { + let output = tokio::process::Command::new(command_name) + .arg("--get-socketpath") + .output() + .await?; + if output.status.success() { + return Ok(std::path::PathBuf::from_str( + String::from_utf8_lossy(&output.stdout).trim_end_matches('\n'), + ) + .unwrap()); + } + } + + Err(tokio::io::Error::new(tokio::io::ErrorKind::Other, ""))? +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[repr(u32)] +pub enum MessageType { + Command = 0, + Workspace = 1, + Subscribe = 2, + Outputs = 3, + Tree = 4, + Marks = 5, + BarConfig = 6, + Version = 7, + BindingModes = 8, + Config = 9, + Tick = 10, + Sync = 11, + BindingState = 12, + #[serde(rename = "workspace")] + SubWorkspace = 0 | 1 << 31, + #[serde(rename = "output")] + SubOutput = 1 | 1 << 31, + #[serde(rename = "mode")] + SubMode = 2 | 1 << 31, + #[serde(rename = "window")] + SubWindow = 3 | 1 << 31, + #[serde(rename = "barconfig_update")] + SubBarConfig = 4 | 1 << 31, + #[serde(rename = "binding")] + SubBinding = 5 | 1 << 31, + #[serde(rename = "shutdown")] + SubShutdown = 6 | 1 << 31, + #[serde(rename = "tick")] + SubTick = 7 | 1 << 31, +} + +impl MessageType { + pub fn is_subscription(&self) -> bool { + ((*self as u32) & (1 << 31)) != 0 + } +} + +impl TryFrom for MessageType { + type Error = &'static str; + fn try_from(value: u32) -> Result { + Ok(match value { + 0x00000000 => Self::Command, + 0x00000001 => Self::Workspace, + 0x00000002 => Self::Subscribe, + 0x00000003 => Self::Outputs, + 0x00000004 => Self::Tree, + 0x00000005 => Self::Marks, + 0x00000006 => Self::BarConfig, + 0x00000007 => Self::Version, + 0x00000008 => Self::BindingModes, + 0x00000009 => Self::Config, + 0x0000000a => Self::Tick, + 0x0000000b => Self::Sync, + 0x0000000c => Self::BindingState, + 0x80000000 => Self::SubWorkspace, + 0x80000001 => Self::SubOutput, + 0x80000002 => Self::SubMode, + 0x80000003 => Self::SubWindow, + 0x80000004 => Self::SubBarConfig, + 0x80000005 => Self::SubBinding, + 0x80000006 => Self::SubShutdown, + 0x80000007 => Self::SubTick, + _ => return Err(""), + }) + } +} + +type SubscriptionCallback = + dyn Fn( + MessageType, + serde_json::Value, + ) -> Pin)>> + Send>>; + +pub struct Connection<'a> { + stream: BufStream, + subscriptions: HashMap>, +} + +impl<'a> Connection<'a> { + pub fn connect(path: &std::path::Path) -> Result { + let stream = std::os::unix::net::UnixStream::connect(path)?; + stream.set_nonblocking(true)?; + let stream = BufStream::new(UnixStream::from_std(stream)?); + let subscriptions = HashMap::new(); + Ok(Self { + stream, + subscriptions, + }) + } + + pub async fn send_message( + &mut self, + message_type: &MessageType, + message: &[u8], + ) -> Result<(), anyhow::Error> { + self.stream.write_all(b"i3-ipc").await?; + self.stream.write_u32_le(message.len() as u32).await?; + self.stream.write_u32_le(*message_type as u32).await?; + self.stream.write_all(message).await?; + self.stream.flush().await?; + Ok(()) + } + + pub async fn receive_message(&mut self) -> Result<(MessageType, Vec), anyhow::Error> { + let mut buffer = vec![0u8; 6]; + self.stream.read_exact(&mut buffer).await?; + if buffer != b"i3-ipc" { + return Err(tokio::io::Error::new(tokio::io::ErrorKind::Other, ""))?; + } + let message_len = self.stream.read_u32_le().await?; + let message_type = self.stream.read_u32_le().await?.try_into().unwrap(); + let mut buffer = vec![0u8; message_len as usize]; + self.stream.read_exact(&mut buffer).await?; + Ok((message_type, buffer)) + } + + pub async fn communicate( + &mut self, + message_type: &MessageType, + message: &[u8], + ) -> Result<(MessageType, serde_json::Value), anyhow::Error> { + self.send_message(message_type, message).await?; + let (message_type, response) = self.receive_message().await?; + Ok(( + message_type, + serde_json::from_str(String::from_utf8_lossy(response.as_ref()).as_ref())?, + )) + } + + pub async fn subscribe( + &mut self, + events: &[MessageType], + callback: &'a SubscriptionCallback, + ) -> Result<(), anyhow::Error> { + let json = serde_json::to_string(events)?; + let (message_type, response) = self + .communicate(&MessageType::Subscribe, json.as_bytes()) + .await?; + for s in events { + self.subscriptions.insert(*s, Box::new(callback)); + } + Ok(()) + } + + pub async fn call_callback( + &mut self, + subscription: &MessageType, + response: serde_json::Value, + ) -> Vec<(MessageType, Vec)> { + let cb = self.subscriptions.get(subscription); + if cb.is_none() { + return Vec::new(); + } + (*cb.unwrap())(*subscription, response).await + } + + pub async fn run(&mut self) -> Result<(), anyhow::Error> { + loop { + let (message_type, response) = self.receive_message().await?; + if !message_type.is_subscription() { + continue; + } + + let json_response = + serde_json::from_str(String::from_utf8_lossy(response.as_ref()).as_ref())?; + let messages: Vec<(MessageType, Vec)> = + self.call_callback(&message_type, json_response).await; + for (message_type, message) in messages { + // TODO maybe log responses? + self.communicate(&message_type, &message).await?; + } + } + } +} + +impl<'a> Clone for Connection<'a> { + fn clone(&self) -> Self { + let path: std::path::PathBuf = self + .stream + .get_ref() + .peer_addr() + .unwrap() + .as_pathname() + .unwrap() + .into(); + Self::connect(path.as_ref()).unwrap() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9fa14b8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use std::cell::RefCell; +use std::rc::Rc; +use tokio::time::{timeout, Duration}; + +mod i3ipc; + +use i3ipc::{Connection, MessageType}; + +fn new_window_cb( + b: MessageType, + c: serde_json::Value, +) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec)>> { + Box::pin(async move { + //println!("{:?}", c); + let environment = rust_lisp::default_env(); + let code = "(= 1 1)"; + let ast = rust_lisp::parser::parse(code).filter_map(|a| a.ok()); + let result = rust_lisp::interpreter::eval_block(Rc::new(RefCell::new(environment)), ast); + + Vec::new() + }) +} + +async fn run<'a>(c: &mut Connection<'a>) -> Result<(), anyhow::Error> { + let resp = c.communicate(&MessageType::Version, b"").await?; + println!("{:?}", resp); + + c.communicate(&MessageType::Command, b"exec alacritty") + .await?; + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<()> { + let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; + let mut sub_connection = connection.clone(); + + sub_connection + .subscribe(&[MessageType::SubWindow], &new_window_cb) + .await?; + tokio::join!( + timeout(Duration::from_secs(1), sub_connection.run()), + run(&mut connection), + ) + .1?; + Ok(()) +} From 0f24273c93e57c7a81af2ea0ae40d33cfb59fa63 Mon Sep 17 00:00:00 2001 From: redxef Date: Sat, 14 Oct 2023 02:08:41 +0200 Subject: [PATCH 2/9] Begin integrating lisp. --- src/main.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9fa14b8..722368f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use anyhow::Result; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use tokio::time::{timeout, Duration}; @@ -7,16 +8,133 @@ mod i3ipc; use i3ipc::{Connection, MessageType}; +fn serde_lisp_value(value: &serde_json::Value) -> rust_lisp::model::Value { + match value { + serde_json::Value::Null => rust_lisp::model::Value::NIL, + serde_json::Value::Bool(b) => { + if *b { + rust_lisp::model::Value::True + } else { + rust_lisp::model::Value::False + } + } + serde_json::Value::Number(n) => { + if n.is_i64() { + rust_lisp::model::Value::Int(n.as_i64().unwrap() as rust_lisp::model::IntType) + } else if n.is_u64() { + rust_lisp::model::Value::Int(n.as_u64().unwrap() as rust_lisp::model::IntType) + } else if n.is_f64() { + rust_lisp::model::Value::Float(n.as_f64().unwrap() as rust_lisp::model::FloatType) + } else { + panic!("should never happen"); + } + } + serde_json::Value::String(s) => rust_lisp::model::Value::String(s.clone()), + serde_json::Value::Array(a) => { + let mut l = rust_lisp::model::List::NIL; + for li in a.into_iter().rev() { + l = l.cons(serde_lisp_value(li)); + } + rust_lisp::model::Value::List(l) + } + serde_json::Value::Object(o) => { + let mut r = HashMap::new(); + for (k, v) in o.into_iter() { + let k_ = rust_lisp::model::Value::String(k.clone()); + let v_ = serde_lisp_value(v); + r.insert(k_, v_); + } + rust_lisp::model::Value::HashMap(Rc::new(RefCell::new(r))) + } + } +} + fn new_window_cb( b: MessageType, c: serde_json::Value, + d: bool, ) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec)>> { Box::pin(async move { - //println!("{:?}", c); - let environment = rust_lisp::default_env(); - let code = "(= 1 1)"; + let mut environment = rust_lisp::default_env(); + environment.define( + rust_lisp::model::Symbol::from("__input__"), + serde_lisp_value(&c), + ); + environment.define( + rust_lisp::model::Symbol::from("load"), + rust_lisp::model::Value::NativeClosure(Rc::new(RefCell::new( + move |e: Rc>, args: Vec| { + let path: &String = + rust_lisp::utils::require_typed_arg::<&String>("load", &args, 0)?; + let path = (*path).as_str().split('.'); + let mut i: rust_lisp::model::Value = e + .as_ref() + .borrow() + .get(&rust_lisp::model::Symbol::from("__input__")) + .unwrap(); + for p in path + .into_iter() + .filter(|x| !(*x).eq("")) + .map(|x| rust_lisp::model::Value::String(x.into())) + { + match i { + rust_lisp::model::Value::HashMap(x) => { + if let Some(_i) = x.as_ref().borrow().get(&p) { + i = _i.clone(); + } else { + return Err(rust_lisp::model::RuntimeError { + msg: format!(r#"No such key {:?}"#, p).into(), + }); + } + } + _ => { + return Err(rust_lisp::model::RuntimeError { + msg: format!(r#"No such key {:?}"#, p).into(), + }) + } + }; + } + Ok(i) + }, + ))), + ); + environment.define( + rust_lisp::model::Symbol::from("has-key"), + rust_lisp::model::Value::NativeClosure(Rc::new(RefCell::new( + move |e: Rc>, args: Vec| { + let path: &String = + rust_lisp::utils::require_typed_arg::<&String>("has-key", &args, 0)?; + let path = (*path).as_str().split('.'); + let mut i: rust_lisp::model::Value = e + .as_ref() + .borrow() + .get(&rust_lisp::model::Symbol::from("__input__")) + .unwrap(); + for p in path + .into_iter() + .filter(|x| !(*x).eq("")) + .map(|x| rust_lisp::model::Value::String(x.into())) + { + match i { + rust_lisp::model::Value::HashMap(x) => { + if let Some(_i) = x.as_ref().borrow().get(&p) { + i = _i.clone(); + } else { + return Ok(rust_lisp::model::Value::False); + } + } + _ => return Ok(rust_lisp::model::Value::False), + }; + } + Ok(rust_lisp::model::Value::True) + }, + ))), + ); + let environment = environment; + let code = r#"(load ".container.geometry")"#; let ast = rust_lisp::parser::parse(code).filter_map(|a| a.ok()); let result = rust_lisp::interpreter::eval_block(Rc::new(RefCell::new(environment)), ast); + println!("{:?}", result); Vec::new() }) @@ -36,8 +154,10 @@ async fn main() -> Result<()> { let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; let mut sub_connection = connection.clone(); + let b_ = true; + let cb = move |a, b| {new_window_cb(a,b,b_)}; sub_connection - .subscribe(&[MessageType::SubWindow], &new_window_cb) + .subscribe(&[MessageType::SubWindow], &cb) .await?; tokio::join!( timeout(Duration::from_secs(1), sub_connection.run()), From 23dc47a008e19b4e533cd10b7a69d4d662d4f7d3 Mon Sep 17 00:00:00 2001 From: redxef Date: Sat, 14 Oct 2023 23:54:06 +0200 Subject: [PATCH 3/9] Mvp finished, can run programs and move them to windows. --- Cargo.toml | 3 +- src/config.rs | 67 ++++++++++++++++++++ src/lisp.rs | 120 ++++++++++++++++++++++++++++++++++++ src/main.rs | 168 +++++++++++--------------------------------------- 4 files changed, 224 insertions(+), 134 deletions(-) create mode 100644 src/config.rs create mode 100644 src/lisp.rs diff --git a/Cargo.toml b/Cargo.toml index f71e683..97dcbe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,8 @@ edition = "2021" anyhow = "1.0.75" byteorder = "1.5.0" futures = "0.3.28" -rust_lisp = "0.18.0" +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_json = "1.0.107" +serde_yaml = "0.9.25" tokio = { version = "1.33.0", features = ["full"] } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1a9ff75 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,67 @@ +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use rust_lisp::model::Value as RValue; + +#[derive(Clone, Debug)] +pub struct Value(Vec); +unsafe impl Send for Value {} +unsafe impl Sync for Value {} + +impl Into for RValue { + fn into(self) -> Value { + Value(vec![self]) + } +} + +impl Into> for Value { + fn into(self) -> Vec { + self.0 + } +} + +impl<'de> Deserialize<'de> for Value { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de> + { + let s: String = Deserialize::deserialize(deserializer)?; + let r: Vec = rust_lisp::parser::parse(&s).filter_map(|x| x.ok()).collect(); + Ok(Value(r)) + } +} + + +#[derive(Clone, Debug)] +#[derive(Deserialize)] +pub struct Program { + #[serde(rename = "match")] + pub match_: Value, + pub cmd: String, + #[serde(default)] + pub run: Option, +} +unsafe impl Send for Program {} +unsafe impl Sync for Program {} + +#[derive(Clone, Debug)] +#[derive(Deserialize)] +pub struct Config { + #[serde(default = "Config::default_timeout")] + pub timeout: u32, + #[serde(default = "Config::default_init")] + pub init: Value, + #[serde(default = "Config::default_programs")] + pub programs: Vec, +} +unsafe impl Send for Config {} +unsafe impl Sync for Config {} +impl Config { + fn default_timeout() -> u32 { + 3000 + } + fn default_init() -> Value { + Value(vec![]) + } + fn default_programs() -> Vec { + vec![] + } +} diff --git a/src/lisp.rs b/src/lisp.rs new file mode 100644 index 0000000..2715cc9 --- /dev/null +++ b/src/lisp.rs @@ -0,0 +1,120 @@ +use std::collections::HashMap; +use rust_lisp::model::{IntType, FloatType, Value, List, Env, reference, reference::Reference}; + + + +fn serde_lisp_value(value: &serde_json::Value) -> Value { + match value { + serde_json::Value::Null => Value::NIL, + serde_json::Value::Bool(b) => { + if *b { + Value::True + } else { + Value::False + } + } + serde_json::Value::Number(n) => { + if n.is_i64() { + Value::Int(n.as_i64().unwrap() as IntType) + } else if n.is_u64() { + Value::Int(n.as_u64().unwrap() as IntType) + } else if n.is_f64() { + Value::Float(n.as_f64().unwrap() as FloatType) + } else { + panic!("should never happen"); + } + } + serde_json::Value::String(s) => Value::String(s.clone()), + serde_json::Value::Array(a) => { + let mut l = List::NIL; + for li in a.into_iter().rev() { + l = l.cons(serde_lisp_value(li)); + } + Value::List(l) + } + serde_json::Value::Object(o) => { + let mut r = HashMap::new(); + for (k, v) in o.into_iter() { + let k_ = Value::String(k.clone()); + let v_ = serde_lisp_value(v); + r.insert(k_, v_); + } + Value::HashMap(reference::new(r)) + } + } +} + +pub fn env(value: &serde_json::Value) -> Env { + let mut environment = rust_lisp::default_env(); + environment.define( + rust_lisp::model::Symbol::from("__input__"), + serde_lisp_value(value), + ); + environment.define( + rust_lisp::model::Symbol::from("load"), + rust_lisp::model::Value::NativeClosure(reference::new( + move |e: Reference, args: Vec| { + let path: &String = + rust_lisp::utils::require_typed_arg::<&String>("load", &args, 0)?; + let path = (*path).as_str().split('.'); + let mut i: rust_lisp::model::Value = reference::borrow(&e) + .get(&rust_lisp::model::Symbol::from("__input__")) + .unwrap(); + for p in path + .into_iter() + .filter(|x| !(*x).eq("")) + .map(|x| rust_lisp::model::Value::String(x.into())) + { + match i { + rust_lisp::model::Value::HashMap(x) => { + if let Some(_i) = reference::borrow(&x).get(&p) { + i = _i.clone(); + } else { + return Err(rust_lisp::model::RuntimeError { + msg: format!(r#"No such key {:?}"#, p).into(), + }); + } + } + _ => { + return Err(rust_lisp::model::RuntimeError { + msg: format!(r#"No such key {:?}"#, p).into(), + }) + } + }; + } + Ok(i) + }, + )), + ); + environment.define( + rust_lisp::model::Symbol::from("has-key"), + rust_lisp::model::Value::NativeClosure(reference::new( + move |e: Reference, args: Vec| { + let path: &String = + rust_lisp::utils::require_typed_arg::<&String>("has-key", &args, 0)?; + let path = (*path).as_str().split('.'); + let mut i: rust_lisp::model::Value = reference::borrow(&e) + .get(&rust_lisp::model::Symbol::from("__input__")) + .unwrap(); + for p in path + .into_iter() + .filter(|x| !(*x).eq("")) + .map(|x| rust_lisp::model::Value::String(x.into())) + { + match i { + rust_lisp::model::Value::HashMap(x) => { + if let Some(_i) = reference::borrow(&x).get(&p) { + i = _i.clone(); + } else { + return Ok(rust_lisp::model::Value::False); + } + } + _ => return Ok(rust_lisp::model::Value::False), + }; + } + Ok(rust_lisp::model::Value::True) + }, + )), + ); + environment +} diff --git a/src/main.rs b/src/main.rs index 722368f..b2a1634 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,151 +1,47 @@ use anyhow::Result; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; use tokio::time::{timeout, Duration}; +use tokio::io::AsyncReadExt; +mod config; mod i3ipc; +mod lisp; +use config::Config; use i3ipc::{Connection, MessageType}; -fn serde_lisp_value(value: &serde_json::Value) -> rust_lisp::model::Value { - match value { - serde_json::Value::Null => rust_lisp::model::Value::NIL, - serde_json::Value::Bool(b) => { - if *b { - rust_lisp::model::Value::True - } else { - rust_lisp::model::Value::False - } - } - serde_json::Value::Number(n) => { - if n.is_i64() { - rust_lisp::model::Value::Int(n.as_i64().unwrap() as rust_lisp::model::IntType) - } else if n.is_u64() { - rust_lisp::model::Value::Int(n.as_u64().unwrap() as rust_lisp::model::IntType) - } else if n.is_f64() { - rust_lisp::model::Value::Float(n.as_f64().unwrap() as rust_lisp::model::FloatType) - } else { - panic!("should never happen"); - } - } - serde_json::Value::String(s) => rust_lisp::model::Value::String(s.clone()), - serde_json::Value::Array(a) => { - let mut l = rust_lisp::model::List::NIL; - for li in a.into_iter().rev() { - l = l.cons(serde_lisp_value(li)); - } - rust_lisp::model::Value::List(l) - } - serde_json::Value::Object(o) => { - let mut r = HashMap::new(); - for (k, v) in o.into_iter() { - let k_ = rust_lisp::model::Value::String(k.clone()); - let v_ = serde_lisp_value(v); - r.insert(k_, v_); - } - rust_lisp::model::Value::HashMap(Rc::new(RefCell::new(r))) - } - } -} - fn new_window_cb( b: MessageType, c: serde_json::Value, - d: bool, + config: &Config, ) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec)>> { + let config_ = config.clone(); Box::pin(async move { - let mut environment = rust_lisp::default_env(); - environment.define( - rust_lisp::model::Symbol::from("__input__"), - serde_lisp_value(&c), - ); - environment.define( - rust_lisp::model::Symbol::from("load"), - rust_lisp::model::Value::NativeClosure(Rc::new(RefCell::new( - move |e: Rc>, args: Vec| { - let path: &String = - rust_lisp::utils::require_typed_arg::<&String>("load", &args, 0)?; - let path = (*path).as_str().split('.'); - let mut i: rust_lisp::model::Value = e - .as_ref() - .borrow() - .get(&rust_lisp::model::Symbol::from("__input__")) - .unwrap(); - for p in path - .into_iter() - .filter(|x| !(*x).eq("")) - .map(|x| rust_lisp::model::Value::String(x.into())) - { - match i { - rust_lisp::model::Value::HashMap(x) => { - if let Some(_i) = x.as_ref().borrow().get(&p) { - i = _i.clone(); - } else { - return Err(rust_lisp::model::RuntimeError { - msg: format!(r#"No such key {:?}"#, p).into(), - }); - } - } - _ => { - return Err(rust_lisp::model::RuntimeError { - msg: format!(r#"No such key {:?}"#, p).into(), - }) - } - }; - } - Ok(i) - }, - ))), - ); - environment.define( - rust_lisp::model::Symbol::from("has-key"), - rust_lisp::model::Value::NativeClosure(Rc::new(RefCell::new( - move |e: Rc>, args: Vec| { - let path: &String = - rust_lisp::utils::require_typed_arg::<&String>("has-key", &args, 0)?; - let path = (*path).as_str().split('.'); - let mut i: rust_lisp::model::Value = e - .as_ref() - .borrow() - .get(&rust_lisp::model::Symbol::from("__input__")) - .unwrap(); - for p in path - .into_iter() - .filter(|x| !(*x).eq("")) - .map(|x| rust_lisp::model::Value::String(x.into())) - { - match i { - rust_lisp::model::Value::HashMap(x) => { - if let Some(_i) = x.as_ref().borrow().get(&p) { - i = _i.clone(); - } else { - return Ok(rust_lisp::model::Value::False); - } - } - _ => return Ok(rust_lisp::model::Value::False), - }; - } - Ok(rust_lisp::model::Value::True) - }, - ))), - ); - let environment = environment; - let code = r#"(load ".container.geometry")"#; - let ast = rust_lisp::parser::parse(code).filter_map(|a| a.ok()); - let result = rust_lisp::interpreter::eval_block(Rc::new(RefCell::new(environment)), ast); - println!("{:?}", result); + for p in config_.programs.iter() { + let e = lisp::env(&c); + let init: Vec = config_.init.clone().into(); + let prog: Vec = p.match_.clone().into(); + let m = init.into_iter().chain(prog.into_iter()); + 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 { + return vec![(MessageType::Command, p.cmd.clone().into_bytes())]; + } + } Vec::new() }) } -async fn run<'a>(c: &mut Connection<'a>) -> Result<(), anyhow::Error> { - let resp = c.communicate(&MessageType::Version, b"").await?; +async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> { + let resp = connection.communicate(&MessageType::Version, b"").await?; println!("{:?}", resp); - c.communicate(&MessageType::Command, b"exec alacritty") - .await?; + for p in config.programs.iter() { + if let Some(r) = &p.run { + let (message_type, response) = connection.communicate(&MessageType::Command, r.as_bytes()).await?; + println!("{:?}", (message_type, response)); + } + } Ok(()) } @@ -154,14 +50,20 @@ async fn main() -> Result<()> { let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; let mut sub_connection = connection.clone(); - let b_ = true; - let cb = move |a, b| {new_window_cb(a,b,b_)}; + let mut config = String::new(); + tokio::fs::File::open("/home/redxef/CODE/i3toolwait/i3_autostart.yaml").await?.read_to_string(&mut config).await?; + let config: Config = serde_yaml::from_str(&config)?; + let config = std::sync::Arc::new(config); + + let cb_config = config.clone(); + let cb = move |a, b| {new_window_cb(a, b, &cb_config)}; sub_connection .subscribe(&[MessageType::SubWindow], &cb) .await?; + tokio::join!( - timeout(Duration::from_secs(1), sub_connection.run()), - run(&mut connection), + timeout(Duration::from_millis(config.timeout as u64), sub_connection.run()), + run(&mut connection, &config), ) .1?; Ok(()) From b76d325d60b0f4ac59a805cb4e56ff459eb7aede Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 00:33:55 +0200 Subject: [PATCH 4/9] Add logging. --- Cargo.toml | 3 +++ src/config.rs | 14 +++++++++++++- src/main.rs | 21 ++++++++++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 97dcbe9..7c18d2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,10 @@ edition = "2021" [dependencies] anyhow = "1.0.75" byteorder = "1.5.0" +clap = { version = "4.4.6", features = ["derive"] } +env_logger = "0.10.0" futures = "0.3.28" +log = "0.4.20" 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_json = "1.0.107" diff --git a/src/config.rs b/src/config.rs index 1a9ff75..00b60be 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; use serde::{Serialize, Deserialize, Serializer, Deserializer}; use rust_lisp::model::Value as RValue; @@ -18,6 +19,18 @@ impl Into> 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 { fn deserialize(deserializer: D) -> Result where @@ -29,7 +42,6 @@ impl<'de> Deserialize<'de> for Value { } } - #[derive(Clone, Debug)] #[derive(Deserialize)] pub struct Program { diff --git a/src/main.rs b/src/main.rs index b2a1634..f633986 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use anyhow::Result; use tokio::time::{timeout, Duration}; use tokio::io::AsyncReadExt; +use clap::Parser; +use log::{info, debug, warn}; mod config; mod i3ipc; @@ -9,25 +11,35 @@ mod lisp; use config::Config; use i3ipc::{Connection, MessageType}; + +#[derive(Debug, Clone)] +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { +} + fn new_window_cb( b: MessageType, c: serde_json::Value, config: &Config, + args: &Args, ) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec)>> { let config_ = config.clone(); Box::pin(async move { + debug!("Received window event: {}", &c); for p in config_.programs.iter() { + debug!("Evaluating program: {}", &p.match_); let e = lisp::env(&c); let init: Vec = config_.init.clone().into(); let prog: Vec = p.match_.clone().into(); let m = init.into_iter().chain(prog.into_iter()); 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 { + debug!("Match found"); return vec![(MessageType::Command, p.cmd.clone().into_bytes())]; } } - + debug!("No match found"); Vec::new() }) } @@ -47,6 +59,8 @@ async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), #[tokio::main] async fn main() -> Result<()> { + let args = std::sync::Arc::new(Args::parse()); + env_logger::init_from_env(env_logger::Env::new().filter("I3TOOLWAIT_LOG").write_style("I3TOOLWAIT_LOG_STYLE")); let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; let mut sub_connection = connection.clone(); @@ -56,7 +70,8 @@ async fn main() -> Result<()> { let config = std::sync::Arc::new(config); 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 .subscribe(&[MessageType::SubWindow], &cb) .await?; From 1b6c9e46be4733f2a8a5f47e98a84a4433731178 Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 01:18:00 +0200 Subject: [PATCH 5/9] Add argument parsing and default config file. --- Cargo.toml | 1 + src/main.rs | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c18d2a..b034ae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] } serde_json = "1.0.107" serde_yaml = "0.9.25" tokio = { version = "1.33.0", features = ["full"] } +xdg = "2.5.2" diff --git a/src/main.rs b/src/main.rs index f633986..11b83c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use tokio::time::{timeout, Duration}; use tokio::io::AsyncReadExt; use clap::Parser; use log::{info, debug, warn}; +use std::path::PathBuf; +use std::str::FromStr; mod config; mod i3ipc; @@ -16,6 +18,17 @@ use i3ipc::{Connection, MessageType}; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Args { + #[arg(short, long, value_name = "FILE")] + config: Option, +} + +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( @@ -45,13 +58,24 @@ fn new_window_cb( } async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> { - let resp = connection.communicate(&MessageType::Version, b"").await?; - println!("{:?}", resp); + let (_, resp) = connection.communicate(&MessageType::Version, b"").await?; + info!("i3 version is {}", resp.get("human_readable").unwrap()); for p in config.programs.iter() { if let Some(r) = &p.run { - let (message_type, response) = connection.communicate(&MessageType::Command, r.as_bytes()).await?; - println!("{:?}", (message_type, response)); + let (_, responses) = connection.communicate(&MessageType::Command, r.as_bytes()).await?; + 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(()) @@ -59,16 +83,22 @@ async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), #[tokio::main] async fn main() -> Result<()> { - let args = std::sync::Arc::new(Args::parse()); env_logger::init_from_env(env_logger::Env::new().filter("I3TOOLWAIT_LOG").write_style("I3TOOLWAIT_LOG_STYLE")); - let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?; - 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(); - 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 = 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_args = args.clone(); let cb = move |a, b| {new_window_cb(a, b, &cb_config, &cb_args)}; From 5189ba323b90df198563c306fe39c79a8a0e1509 Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 14:39:59 +0200 Subject: [PATCH 6/9] Add signal processing, more logs. --- Cargo.toml | 1 + src/config.rs | 54 +++++++++++---- src/i3ipc.rs | 22 ++++-- src/lisp.rs | 3 +- src/main.rs | 180 ++++++++++++++++++++++++++++++++++++++------------ 5 files changed, 197 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b034ae7..f832c4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,6 @@ rust_lisp = { git = "https://github.com/brundonsmith/rust_lisp.git", branch = "a serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] } serde_json = "1.0.107" serde_yaml = "0.9.25" +strfmt = "0.2.4" tokio = { version = "1.33.0", features = ["full"] } xdg = "2.5.2" diff --git a/src/config.rs b/src/config.rs index 00b60be..7605c56 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use std::fmt::{Display, Formatter}; -use serde::{Serialize, Deserialize, Serializer, Deserializer}; + use rust_lisp::model::Value as RValue; +use serde::{Deserialize, Deserializer}; #[derive(Clone, Debug)] pub struct Value(Vec); @@ -34,16 +35,17 @@ impl Display for Value { impl<'de> Deserialize<'de> for Value { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de> + D: Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; - let r: Vec = rust_lisp::parser::parse(&s).filter_map(|x| x.ok()).collect(); + let r: Vec = rust_lisp::parser::parse(&s) + .filter_map(|x| x.ok()) + .collect(); Ok(Value(r)) } } -#[derive(Clone, Debug)] -#[derive(Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Program { #[serde(rename = "match")] pub match_: Value, @@ -51,29 +53,53 @@ pub struct Program { #[serde(default)] pub run: Option, } -unsafe impl Send for Program {} -unsafe impl Sync for Program {} -#[derive(Clone, Debug)] -#[derive(Deserialize)] +#[derive(Clone, Debug, Deserialize)] +pub struct Signal { + #[serde(default)] + pub run: Option, + #[serde(default = "Signal::default_timeout")] + pub timeout: u64, +} +impl Signal { + fn default_timeout() -> u64 { + 500 + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum ProgramEntry { + Program(Program), + Signal(Signal), +} + +// Program is only unsafe because Value has dyn Any in it (via Foreign). +// if we don't use !Send in Foreign everything is fine. +unsafe impl Send for Program {} + +#[derive(Clone, Debug, Deserialize)] pub struct Config { #[serde(default = "Config::default_timeout")] - pub timeout: u32, + pub timeout: u64, #[serde(default = "Config::default_init")] pub init: Value, #[serde(default = "Config::default_programs")] - pub programs: Vec, + pub programs: Vec, + #[serde(default)] + pub cmd: Option, } +// Config is only unsafe because Value has dyn Any in it (via Foreign). +// if we don't use !Send in Foreign everything is fine. unsafe impl Send for Config {} -unsafe impl Sync for Config {} impl Config { - fn default_timeout() -> u32 { + fn default_timeout() -> u64 { 3000 } fn default_init() -> Value { Value(vec![]) } - fn default_programs() -> Vec { + fn default_programs() -> Vec { vec![] } } diff --git a/src/i3ipc.rs b/src/i3ipc.rs index 4a6cd7c..b4c2024 100644 --- a/src/i3ipc.rs +++ b/src/i3ipc.rs @@ -1,10 +1,11 @@ -use anyhow::Result; -use futures::future::BoxFuture; -use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::pin::Pin; use std::str::FromStr; -use std::sync::Arc; + + +use anyhow::Result; + +use serde::{Deserialize, Serialize}; use tokio::io::{AsyncReadExt, AsyncWriteExt, BufStream}; use tokio::net::UnixStream; @@ -192,9 +193,18 @@ impl<'a> Connection<'a> { (*cb.unwrap())(*subscription, response).await } - pub async fn run(&mut self) -> Result<(), anyhow::Error> { + pub async fn run( + &mut self, + rx: &mut tokio::sync::broadcast::Receiver<()>, + ) -> Result<(), anyhow::Error> { loop { - let (message_type, response) = self.receive_message().await?; + let stop_task = rx.recv(); + let receive_message_task = self.receive_message(); + let result = tokio::select! { + _ = stop_task => {return Ok(())}, + result = receive_message_task => result?, + }; + let (message_type, response) = result; if !message_type.is_subscription() { continue; } diff --git a/src/lisp.rs b/src/lisp.rs index 2715cc9..3e2a0b4 100644 --- a/src/lisp.rs +++ b/src/lisp.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use rust_lisp::model::{IntType, FloatType, Value, List, Env, reference, reference::Reference}; - +use rust_lisp::model::{reference, reference::Reference, Env, FloatType, IntType, List, Value}; fn serde_lisp_value(value: &serde_json::Value) -> Value { match value { diff --git a/src/main.rs b/src/main.rs index 11b83c0..c88504d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,21 @@ -use anyhow::Result; -use tokio::time::{timeout, Duration}; -use tokio::io::AsyncReadExt; -use clap::Parser; -use log::{info, debug, warn}; +use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; +use anyhow::Result; +use clap::Parser; +use log::{debug, info, warn}; +use tokio::io::AsyncReadExt; +use tokio::time::{timeout, Duration}; + mod config; mod i3ipc; mod lisp; -use config::Config; +use config::{Config, ProgramEntry}; use i3ipc::{Connection, MessageType}; - -#[derive(Debug, Clone)] -#[derive(Parser)] +#[derive(Debug, Clone, Parser)] #[command(author, version, about, long_about = None)] struct Args { #[arg(short, long, value_name = "FILE")] @@ -26,64 +26,138 @@ 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")); + self.config = Some( + xdg::BaseDirectories::with_prefix("i3toolwait") + .unwrap() + .get_config_file("config.yaml"), + ); } } } fn new_window_cb( - b: MessageType, + _b: MessageType, c: serde_json::Value, config: &Config, - args: &Args, + _args: &Args, + programs: &std::sync::Arc>>, + tx: &tokio::sync::broadcast::Sender<()>, ) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec)>> { let config_ = config.clone(); + let tx_ = tx.clone(); + let programs_ = programs.clone(); Box::pin(async move { + let mut command = None; + let mut index = None; debug!("Received window event: {}", &c); - for p in config_.programs.iter() { - debug!("Evaluating program: {}", &p.match_); - let e = lisp::env(&c); - let init: Vec = config_.init.clone().into(); - let prog: Vec = p.match_.clone().into(); - let m = init.into_iter().chain(prog.into_iter()); - let result = rust_lisp::interpreter::eval_block(rust_lisp::model::reference::new(e), m); - if let Ok(rust_lisp::model::Value::True) = result { - debug!("Match found"); - return vec![(MessageType::Command, p.cmd.clone().into_bytes())]; + for (i, p) in programs_.lock().await.iter().enumerate() { + match p { + ProgramEntry::Program(p) => { + debug!("Evaluating program: {}", &p.match_); + let e = lisp::env(&c); + let init: Vec = config_.init.clone().into(); + let prog: Vec = p.match_.clone().into(); + let m = init.into_iter().chain(prog.into_iter()); + let result = + rust_lisp::interpreter::eval_block(rust_lisp::model::reference::new(e), m); + if let Ok(v) = &result { + debug!("Received result: {}", v); + if *v == rust_lisp::model::Value::False { + continue; + } + debug!("Match found"); + let mut vars = HashMap::with_capacity(1); + vars.insert("result".to_string(), v.to_string()); + let cmd = strfmt::strfmt(&p.cmd, &vars).unwrap(); + debug!("Command: {}", &cmd); + + index = Some(i); + command = Some(cmd); + break; + } else { + warn!("Program produced an error: {:?}", &result); + } + } + _ => { + // Ignore signal entries + () + } + }; + } + if let Some(index) = index { + let mut plock = programs_.lock().await; + plock.remove(index); + if plock.len() == 0 { + tx_.send(()).unwrap(); } + return vec![(MessageType::Command, command.unwrap().into_bytes())]; } debug!("No match found"); Vec::new() }) } +async fn run_command<'a>( + connection: &mut Connection<'a>, + command: &str, +) -> Result<(), anyhow::Error> { + let (_, responses) = connection + .communicate(&MessageType::Command, command.as_bytes()) + .await?; + 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 {}: {}", command, response); + } + } + } + } + _ => panic!("invalid response"), + }; + Ok(()) +} + async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> { let (_, resp) = connection.communicate(&MessageType::Version, b"").await?; info!("i3 version is {}", resp.get("human_readable").unwrap()); + let mut signal_stream = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::user_defined1())?; + for p in config.programs.iter() { - if let Some(r) = &p.run { - let (_, responses) = connection.communicate(&MessageType::Command, r.as_bytes()).await?; - 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"), - }; - } + match p { + ProgramEntry::Program(p) => { + if let Some(r) = &p.run { + run_command(connection, r).await?; + } + } + ProgramEntry::Signal(p) => { + if let Some(r) = &p.run { + run_command(connection, r).await?; + } + if let Err(_) = + timeout(Duration::from_millis(p.timeout), signal_stream.recv()).await + { + warn!( + "Ran into timeout when waiting for signal, program: {:?}", + p.run + ); + } + } + }; } Ok(()) } #[tokio::main] async fn main() -> Result<()> { - env_logger::init_from_env(env_logger::Env::new().filter("I3TOOLWAIT_LOG").write_style("I3TOOLWAIT_LOG_STYLE")); + env_logger::init_from_env( + env_logger::Env::new() + .filter("I3TOOLWAIT_LOG") + .write_style("I3TOOLWAIT_LOG_STYLE"), + ); let mut args = Args::parse(); args.finish(); @@ -92,24 +166,48 @@ async fn main() -> Result<()> { 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?; + 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 = std::sync::Arc::new(config); + let programs = std::sync::Arc::new(tokio::sync::Mutex::new(config.programs.clone())); 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_args = args.clone(); - let cb = move |a, b| {new_window_cb(a, b, &cb_config, &cb_args)}; + + let (tx, mut rx) = tokio::sync::broadcast::channel::<()>(1); + + let cb_programs = programs.clone(); + let cb = move |a, b| new_window_cb(a, b, &cb_config, &cb_args, &cb_programs, &tx); sub_connection .subscribe(&[MessageType::SubWindow], &cb) .await?; tokio::join!( - timeout(Duration::from_millis(config.timeout as u64), sub_connection.run()), + timeout( + Duration::from_millis(config.timeout), + sub_connection.run(&mut rx) + ), run(&mut connection, &config), ) .1?; + { + let p = programs.lock().await; + if p.len() != 0 { + warn!("Not all programs consumed: {:?}", &p); + info!("Maybe the timouts are too short?"); + } + } + + if let Some(cmd) = &config.cmd { + connection + .communicate(&MessageType::Command, cmd.as_bytes()) + .await?; + } Ok(()) } From d0ad88af6a9235225f66e31a1a23cb5e0ab50a53 Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 15:13:52 +0200 Subject: [PATCH 7/9] Add readme. --- README.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..af7f385 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# i3toolwait + +Launch a program and move it to the correct workspace. + +## Usage + +`i3toolwait -c FILE` + +Optionally start multiple programs and wait for their windows to appear. +Once these windows appeared a custom i3 command can be specified. + +## Example + +```yaml +--- +timeout: 10000 +init: | + (begin + (define i3_path ".container.window_properties.class") + (define sway_path ".container.app_id") + (defun idmatch (name) (== (if (has-key sway_path) (load sway_path) (load i3_path)) name)) + (defun match (name) (and (== (load ".change") "new") (idmatch name))) + (defun match-load (name) (if (match name) (load ".container.id") F)) + ) +cmd: 'workspace 1' +programs: +- run: 'exec gtk-launch librewolf' + cmd: 'for_window [con_id="{result}"] focus; move container to workspace 1' + match: '(match-load "LibreWolf")' +- run: 'exec gtk-launch nheko || gtk-launch io.element.Element' + cmd: 'for_window [con_id="{result}"] focus; move container to workspace 2' + match: '(if (or (match "Electron") (match "nheko")) (load ".container.id") F)' +- run: 'exec gtk-launch thunderbird' + cmd: 'for_window [con_id="{result}"] focus; move container to workspace 3' + match: '(match-load "thunderbird")' +- run: 'exec nm-applet --indicator' +- run: 'exec blueman-applet' +- run: 'exec gtk-launch org.kde.kdeconnect.nonplasma' +- run: 'exec gtk-launch syncthing-gtk' +``` + +## Configuration + +The configuration file is in YAML format. + + +### Configuration + +#### timeout: int + +_Optional_ _Default_ `3000` + +Total program timeout in ms. + +#### init: String + +_Optional_ _Default_ `""` + +Initialization program; Used to initialize the environment, useful +to define custom functions which should be available everywhere. + +#### cmd: String + +_Optional_ _Default_ `""` + +A final i3 command to be executed before exiting. + +#### programs: List[Union[[Program](#program), [Signal](#signal)]] + +_Optional_ _Default_ `[]` + +A list of programs to execute. + +### Program + +Launch all programs using [`run`](#run-string) and execute +[`cmd`](#cmd-string-1) once [`match`](#match-string) matches +a window. + +#### match: String + +_Required_ + +A lisp program which analyzes the i3 window event and returns a value. +If the return value is `false` the window does not match and no +further processing occurs. Otherwise the i3 command +[`cmd`](#cmd-string-1). +will be executed. + +#### cmd: String + +_Required_ + +A i3 command. Can contain a format `{result}` which gets replaced +by the output of the match command. + +**Example:** + +`for_window [con_id="{result}"] focus; move container to window 1` + +#### run: String + +_Optional_ _Default_ `null` + +A i3 command which is run at program startup, can be used to launch +programs. + +**Example:** + +`exec gtk-launch firefox` + +### Signal + +Programs are launched in order and only advance after +[`timeout`](#timeout-int-1) or after receiving signal +`SIGUSR1`. + +#### run: String + +_Optional_ _Default_ `null` + +A i3 command. + +#### timeout: int + +_Optional_ _Default_ `500` + +How long to wait for the signal in ms. From 1cb9672b4861dbc0085f19aa7f0bc1c9a74cf6f3 Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 15:14:10 +0200 Subject: [PATCH 8/9] Reorder config fields. --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7605c56..4b2f237 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,10 +84,10 @@ pub struct Config { pub timeout: u64, #[serde(default = "Config::default_init")] pub init: Value, - #[serde(default = "Config::default_programs")] - pub programs: Vec, #[serde(default)] pub cmd: Option, + #[serde(default = "Config::default_programs")] + pub programs: Vec, } // Config is only unsafe because Value has dyn Any in it (via Foreign). // if we don't use !Send in Foreign everything is fine. From 114e7892d751bd2ce74b2da0bf4bcd23317c1caa Mon Sep 17 00:00:00 2001 From: redxef Date: Sun, 15 Oct 2023 15:16:19 +0200 Subject: [PATCH 9/9] Add cargo.lock. --- Cargo.lock | 851 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 851 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ec9390d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,851 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "i3toolwait" +version = "0.1.0" +dependencies = [ + "anyhow", + "byteorder", + "clap", + "env_logger", + "futures", + "log", + "rust_lisp", + "serde", + "serde_json", + "serde_yaml", + "strfmt", + "tokio", + "xdg", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rust_lisp" +version = "0.18.0" +source = "git+https://github.com/brundonsmith/rust_lisp.git?branch=arc-feature-addition#6c4445965c027bd4d3cf1f3154e9145bd45e8ba6" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"