Initial rust rewrite commit, still wip.
This commit is contained in:
commit
81790d4308
4 changed files with 290 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -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"] }
|
226
src/i3ipc.rs
Normal file
226
src/i3ipc.rs
Normal file
|
@ -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<std::path::PathBuf, anyhow::Error> {
|
||||
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<u32> for MessageType {
|
||||
type Error = &'static str;
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
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<Box<dyn std::future::Future<Output = Vec<(MessageType, Vec<u8>)>> + Send>>;
|
||||
|
||||
pub struct Connection<'a> {
|
||||
stream: BufStream<UnixStream>,
|
||||
subscriptions: HashMap<MessageType, Box<&'a SubscriptionCallback>>,
|
||||
}
|
||||
|
||||
impl<'a> Connection<'a> {
|
||||
pub fn connect(path: &std::path::Path) -> Result<Self, anyhow::Error> {
|
||||
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<u8>), 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<u8>)> {
|
||||
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<u8>)> =
|
||||
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()
|
||||
}
|
||||
}
|
48
src/main.rs
Normal file
48
src/main.rs
Normal file
|
@ -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<u8>)>> {
|
||||
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(())
|
||||
}
|
Loading…
Reference in a new issue