ksway/client.rs
1use std::io::Read;
2use std::os::unix::net::UnixStream;
3use std::path::{Path, PathBuf};
4use std::time::Duration;
5
6use byteorder::{NativeEndian, ReadBytesExt};
7use crossbeam_channel as chan;
8use num_traits::FromPrimitive;
9
10use crate::ipc_command;
11use crate::{guess_sway_socket_path, Error, IpcCommand, IpcEvent, Result};
12
13pub struct Client {
14 socket: UnixStream,
15 socket_path: PathBuf,
16 subscription_events: Option<chan::Sender<(IpcEvent, Vec<u8>)>>,
17}
18
19type RawResponse = (u32, Vec<u8>);
20
21impl Client {
22 /// The socket path that we are currently connected to.
23 pub fn socket_path(&self) -> &Path {
24 &self.socket_path
25 }
26
27 /// Connect to a specific socket.
28 pub fn connect_to_path<P: Into<PathBuf>>(path: P) -> Result<Self> {
29 let path = path.into();
30 let socket = UnixStream::connect(&path)?;
31 // socket.set_nonblocking(true)?;
32 socket.set_read_timeout(Some(Duration::from_secs(1)))?;
33 Ok(Self {
34 socket,
35 socket_path: path,
36 subscription_events: None,
37 })
38 }
39
40 /// Guess which socket to connect to using `ksway::guess_sway_socket_path()`.
41 /// This first checks for SWAYSOCK environment variable, or tries to find an appropriate
42 /// socket when run outside of a graphical environment. See `guess_sway_socket_path()` for more.
43 pub fn connect() -> Result<Self> {
44 Self::connect_to_path(guess_sway_socket_path()?)
45 }
46
47 /// Call this to check for new subscription events from the socket.
48 pub fn poll(&mut self) -> Result<()> {
49 let (payload_type, payload) = match self.read_response() {
50 Ok(value) => value,
51 // EAGAIN/EWOULDBLOCK means there's no data right now, but this isn't
52 // an error for us in this scenario since we are checking with a timeout.
53 Err(Error::Io(ref err)) if err.raw_os_error() == Some(11) => return Ok(()),
54 err => err?,
55 };
56 if payload_type & IpcEvent::Workspace as u32 > 0 {
57 if let Some(ref tx) = self.subscription_events {
58 tx.send((IpcEvent::from_u32(payload_type).unwrap(), payload))
59 .map_err(|_| Error::SubscriptionError)?;
60 }
61 } else {
62 // TODO figure out
63 unreachable!();
64 // return Ok(payload);
65 }
66 Ok(())
67 }
68
69 fn read_response(&mut self) -> Result<RawResponse> {
70 let mut buffer = *b"i3-ipc";
71 self.socket.read_exact(&mut buffer).map_err(Error::Io)?;
72 debug_assert_eq!(b"i3-ipc", &buffer);
73 let payload_length = self.socket.read_u32::<NativeEndian>().map_err(Error::Io)?;
74 let payload_type = self.socket.read_u32::<NativeEndian>().map_err(Error::Io)?;
75 let mut buffer = vec![0u8; payload_length as usize];
76 self.socket.read_exact(&mut buffer).map_err(Error::Io)?;
77 let payload = (payload_type, buffer);
78 Ok(payload)
79 }
80
81 fn send_command(&mut self, command: IpcCommand) -> Result<()> {
82 command.write(&mut self.socket).map_err(Error::Io)?;
83 Ok(())
84 }
85
86 /// Send an ipc command. Used with the IpcCommand enum or constructed from the convenience
87 /// methods under ksway::ipc_command::*
88 /// An alias for `client.ipc(ipc_command::run(...))` is provided at `client.run(...)`
89 ///
90 /// The result is immediately read, aka this is a synchronous call.
91 /// The raw bytes are returned in order to avoid dependency on any particular json
92 /// implementation.
93 pub fn ipc(&mut self, command: IpcCommand) -> Result<Vec<u8>> {
94 let code = command.code() as u32;
95 self.send_command(command)?;
96 loop {
97 let (payload_type, payload) = self.read_response()?;
98 if payload_type & IpcEvent::Workspace as u32 > 0 {
99 if let Some(ref tx) = self.subscription_events {
100 tx.send((IpcEvent::from_u32(payload_type).unwrap(), payload))
101 .map_err(|_| Error::SubscriptionError)?;
102 }
103 } else {
104 debug_assert_eq!(code, payload_type);
105 return Ok(payload);
106 }
107 }
108 }
109
110 /// Alias for `client.ipc(ipc_command::run(...))`. Accepts any string as a parameter, which
111 /// would be equivalent to `swaymsg $command`, but some type safety and convenience is provided
112 /// via `ksway::Command` and `ksway::command::*` (which provides a function interface instead
113 /// of an enum)
114 ///
115 /// The result is immediately read, aka this is a synchronous call.
116 /// The raw bytes are returned in order to avoid dependency on any particular json
117 /// implementation.
118 pub fn run<T: ToString>(&mut self, command: T) -> Result<Vec<u8>> {
119 self.ipc(ipc_command::run(command.to_string()))
120 }
121
122 /// Subscribe to events from sway. You can only subscribe once for a client connection, but
123 /// there's really no point to subscribing multiple times. It will return
124 /// Error::AlreadySubscribed if you attempt to do so.
125 ///
126 /// Returns a crossbeam channel that you can use to poll for events.
127 ///
128 /// In order to receive events, you must call `client.poll()` to check for new subscription
129 /// events. You can see an example of this in the examples.
130 /// A minimal loop is as such:
131 /// ```no_run
132 /// use ksway::IpcEvent;
133 ///
134 /// let mut client = ksway::Client::connect()?;
135 ///
136 /// let rx = client.subscribe(vec![IpcEvent::Window, IpcEvent::Tick])?;
137 /// loop {
138 /// while let Ok((payload_type, payload)) = rx.try_recv() {
139 /// match payload_type {
140 /// IpcEvent::Window => {},
141 /// _ => {},
142 /// }
143 /// }
144 /// client.poll()?;
145 /// }
146 /// # Ok::<(), ksway::Error>(())
147 /// ```
148 pub fn subscribe(
149 &mut self,
150 event_types: Vec<IpcEvent>,
151 ) -> Result<chan::Receiver<(IpcEvent, Vec<u8>)>> {
152 if self.subscription_events.is_some() {
153 return Err(Error::AlreadySubscribed);
154 }
155 let (tx, rx) = chan::unbounded();
156 self.subscription_events = Some(tx);
157 self.ipc(ipc_command::subscribe(event_types))?;
158
159 Ok(rx)
160 }
161}