1use 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
17pub 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
69pub 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 let mut intbuf = [0_u8; 4];
82 self.read_exact(&mut intbuf)?;
83 let len = u32::from_ne_bytes(intbuf);
84 let mut msgbuf = [0_u8; 4];
86 self.read_exact(&mut msgbuf)?;
87 let msgtype = u32::from_ne_bytes(msgbuf);
88 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
101impl I3IPC for SyncUnixStream {}
103
104impl I3Protocol for SyncUnixStream {}
105
106#[derive(Debug)]
114pub struct MsgResponse<D> {
115 pub msg_type: msg::Msg,
116 pub body: D,
117}
118
119impl<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
129pub 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
145pub 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}