i3ipc_types/
lib.rs

1//! Crate contains types and implementations for communicating with i3.
2//! Also contained is protocol level communication using `io::Read` and `Write`
3use serde::{de::DeserializeOwned, Serialize};
4
5use std::{env, io, os::unix::net::UnixStream as SyncUnixStream, process::Command};
6
7#[cfg(feature = "async-traits")]
8use tokio::net::UnixStream as TokioUnixStream;
9
10#[cfg(feature = "async-std-traits")]
11use async_std::os::unix::net::UnixStream as AsyncStdUnixStream;
12
13pub mod event;
14pub mod msg;
15pub mod reply;
16
17/// Types implementing this are provided a connect function and return a stream
18pub trait Connect {
19    type Stream: I3IPC;
20    fn connect() -> io::Result<Self::Stream>;
21}
22
23pub const MAGIC: &str = "i3-ipc";
24
25pub trait I3Protocol {
26    const MAGIC: &'static str = MAGIC;
27
28    fn _encode_msg<P>(&self, msg: msg::Msg, payload: Option<P>) -> Vec<u8>
29    where
30        P: AsRef<str>,
31    {
32        let mut buf = Vec::with_capacity(14);
33        buf.extend(<Self as I3Protocol>::MAGIC.as_bytes());
34        if let Some(p) = &payload {
35            buf.extend((p.as_ref().len() as u32).to_ne_bytes());
36        } else {
37            buf.extend((0_u32).to_ne_bytes());
38        }
39        buf.extend(<u32 as From<msg::Msg>>::from(msg).to_ne_bytes());
40        if let Some(p) = &payload {
41            buf.extend(p.as_ref().as_bytes());
42        }
43        buf
44    }
45
46    fn encode_msg(&self, msg: msg::Msg) -> Vec<u8> {
47        self._encode_msg::<&str>(msg, None)
48    }
49
50    fn encode_msg_body<P>(&self, msg: msg::Msg, payload: P) -> Vec<u8>
51    where
52        P: AsRef<str>,
53    {
54        self._encode_msg(msg, Some(payload))
55    }
56
57    fn encode_msg_json<P>(&self, msg: msg::Msg, payload: P) -> io::Result<Vec<u8>>
58    where
59        P: Serialize,
60    {
61        Ok(self.encode_msg_body(msg, serde_json::to_string(&payload)?))
62    }
63
64    fn decode_event(evt_type: u32, payload: Vec<u8>) -> io::Result<event::Event> {
65        decode_event(evt_type, payload)
66    }
67}
68
69/// Trait containing methods to encode and decode message from i3
70pub trait I3IPC: io::Read + io::Write + I3Protocol {
71    fn decode_msg(&mut self) -> io::Result<(u32, Vec<u8>)> {
72        let mut buf = [0_u8; 6];
73        self.read_exact(&mut buf)?;
74        if &buf[..] != <Self as I3Protocol>::MAGIC.as_bytes() {
75            return Err(io::Error::new(
76                io::ErrorKind::InvalidData,
77                format!("Expected 'i3-ipc' but received: {buf:?}"),
78            ));
79        }
80        // get payload len
81        let mut intbuf = [0_u8; 4];
82        self.read_exact(&mut intbuf)?;
83        let len = u32::from_ne_bytes(intbuf);
84        // get msg type
85        let mut msgbuf = [0_u8; 4];
86        self.read_exact(&mut msgbuf)?;
87        let msgtype = u32::from_ne_bytes(msgbuf);
88        // get payload
89        let mut payload_buf = vec![0_u8; len as usize];
90        self.read_exact(&mut payload_buf)?;
91        Ok((msgtype, payload_buf))
92    }
93}
94
95#[cfg(feature = "async-traits")]
96impl I3Protocol for TokioUnixStream {}
97
98#[cfg(feature = "async-std-traits")]
99impl I3Protocol for AsyncStdUnixStream {}
100
101/// implement I3IPC for std UnixStream
102impl I3IPC for SyncUnixStream {}
103
104impl I3Protocol for SyncUnixStream {}
105
106// #[cfg(not(feature = "async-traits"))]
107/// Instead of returning an enum, we're returning a struct containing the `Msg`
108/// type and some body. An advantage to this over the enum method is that there
109/// is no minimum memory size that we must have. This is helpful when some
110/// variants are very large compared to others, as in the case of say
111/// [reply::Node](reply/struct.Node.html) vs
112/// [reply::Config](reply/struct.Config.html)
113#[derive(Debug)]
114pub struct MsgResponse<D> {
115    pub msg_type: msg::Msg,
116    pub body: D,
117}
118
119/// `MsgResponse` is valid for anything which can be deserialized with serde
120impl<D: DeserializeOwned> MsgResponse<D> {
121    pub fn new(msg_type: u32, buf: Vec<u8>) -> io::Result<Self> {
122        Ok(MsgResponse {
123            msg_type: msg_type.into(),
124            body: serde_json::from_slice(&buf[..])?,
125        })
126    }
127}
128
129/// get socket path from i3
130pub fn socket_path() -> io::Result<String> {
131    if let Ok(p) = env::var("I3SOCK") {
132        return Ok(p);
133    }
134    let out = Command::new("i3").arg("--get-socketpath").output()?;
135    if out.status.success() {
136        Ok(String::from_utf8_lossy(&out.stdout).trim_end().to_string())
137    } else {
138        Err(io::Error::new(
139            io::ErrorKind::BrokenPipe,
140            "Unable to get i3 socket path",
141        ))
142    }
143}
144
145/// Given an event type and payload this function will deserialize the proper
146/// struct
147pub fn decode_event<P>(evt_type: u32, payload: P) -> io::Result<event::Event>
148where
149    P: AsRef<[u8]>,
150{
151    use event::{Event, Subscribe};
152    let evt_type = evt_type & !(1 << 31);
153    let body = match evt_type.into() {
154        Subscribe::Workspace => Event::Workspace(Box::new(serde_json::from_slice::<
155            event::WorkspaceData,
156        >(payload.as_ref())?)),
157        Subscribe::Output => Event::Output(serde_json::from_slice::<event::OutputData>(
158            payload.as_ref(),
159        )?),
160        Subscribe::Mode => {
161            Event::Mode(serde_json::from_slice::<event::ModeData>(payload.as_ref())?)
162        }
163        Subscribe::Window => Event::Window(Box::new(serde_json::from_slice::<event::WindowData>(
164            payload.as_ref(),
165        )?)),
166        Subscribe::BarConfigUpdate => Event::BarConfig(serde_json::from_slice::<
167            event::BarConfigData,
168        >(payload.as_ref())?),
169        Subscribe::Binding => Event::Binding(serde_json::from_slice::<event::BindingData>(
170            payload.as_ref(),
171        )?),
172        Subscribe::Shutdown => Event::Shutdown(serde_json::from_slice::<event::ShutdownData>(
173            payload.as_ref(),
174        )?),
175        Subscribe::Tick => {
176            Event::Tick(serde_json::from_slice::<event::TickData>(payload.as_ref())?)
177        }
178    };
179    Ok(body)
180}