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}