diff --git a/src/lib.rs b/src/lib.rs index 1e0ba0b..5e6d47a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,10 @@ mod types; use std::os::unix::net::UnixStream; use std::io::prelude::*; -use types::workspace::Workspace; +use std::io::{IoSlice, Result, Error, ErrorKind}; + +use serde::de::DeserializeOwned; +use types::{Success, Workspace, Output, Version , Config, Input}; pub const IPC_RUN_COMMAND: u32 = 0; pub const IPC_GET_WORKSPACES: u32 = 1; @@ -22,140 +25,269 @@ pub const IPC_GET_SEATS: u32 = 101; pub struct I3msg { magic_string: &'static str, - buffer: Vec, - response: Vec, + stream: UnixStream, } impl I3msg { - pub fn new() -> I3msg { + pub fn new(socket: &str) -> I3msg { + let stream = UnixStream::connect(socket).unwrap(); return I3msg { magic_string: "i3-ipc", - buffer: Vec::new(), - response: Vec::new(), + stream: stream } } /// Runs the payload as sway commands - pub fn run_command(&mut self, socket: &mut UnixStream, payload: &str) -> Vec { - self.send_command(socket, payload, IPC_RUN_COMMAND) + pub fn run_command(&mut self, payload: &str) -> Result> { + self.run(payload, IPC_RUN_COMMAND) } /// Get the list of current workspaces - pub fn get_workspaces(&mut self, socket: &mut UnixStream) -> Vec { - let response = self.send_command(socket, "get_workspaces", IPC_GET_WORKSPACES); - serde_json::from_slice::>(&response[14..]).unwrap() + pub fn get_workspaces(&mut self) -> Result> { + self.run("get_workspaces", IPC_GET_WORKSPACES) } /// Subscribe the IPC connection to the event listed in the payload - pub fn subscribe(&mut self, socket: &mut UnixStream, payload: &str) -> Vec { - self.send_command(socket, payload, IPC_SUBSCRIBE); - todo!(); + pub fn subscribe(&mut self, payload: &str) -> Result { + self.run(payload, IPC_SUBSCRIBE) } /// Get the list of current outputs - pub fn get_outputs(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_outputs", IPC_GET_OUTPUTS) + pub fn get_outputs(&mut self) -> Result> { + self.run("get_outputs", IPC_GET_OUTPUTS) } /// Get the node layout tree - pub fn get_tree(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_tree", IPC_GET_TREE) + pub fn get_tree(&mut self) -> Result> { + self.run("get_tree", IPC_GET_TREE) } /// Get the names of all the marks currently set - pub fn get_marks(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_marks", IPC_GET_MARKS) + pub fn get_marks(&mut self) -> Result> { + self.run("get_marks", IPC_GET_MARKS) } /// Get the specified bar config or a list of bar config names - pub fn get_bar_config(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_bar_config", IPC_GET_BAR_CONFIG) + pub fn get_bar_config(&mut self, payload: &str) -> Result> { + self.run(payload, IPC_GET_BAR_CONFIG) } /// Get the version of sway that owns the IPC socket - pub fn get_version(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_version", IPC_GET_VERSION) + pub fn get_version(&mut self) -> Result { + self.run("get_version", IPC_GET_VERSION) } /// Get the list of binding mode names - pub fn get_binding_modes(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_binding_modes", IPC_GET_BINDING_MODES) + pub fn get_binding_modes(&mut self) -> Result> { + self.run("get_binding_modes", IPC_GET_BINDING_MODES) } /// Returns the config that was last loaded - pub fn get_config(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_config", IPC_GET_CONFIG) + pub fn get_config(&mut self) -> Result { + self.run("get_config", IPC_GET_CONFIG) } /// Sends a tick event with specified payload - pub fn send_tick(&mut self, socket: &mut UnixStream, payload: &str) -> Vec { - self.send_command(socket, payload, IPC_SEND_TICK); - todo!() + pub fn send_tick(&mut self, payload: &str) -> Result { + self.run(payload, IPC_SEND_TICK) } /// Replies failure object for i3 compatibility - pub fn sync(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "sync", IPC_SYNC) + pub fn sync(&mut self) -> Result { + self.run("sync", IPC_SYNC) } /// Request the current binding state, e.g. the currently active binding mode name - pub fn get_binding_state(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_binding_state", IPC_GET_BINDING_STATE) + pub fn get_binding_state(&mut self) -> Result> { + self.run("get_binding_state", IPC_GET_BINDING_STATE) } /// Get the list of input devices - pub fn get_inputs(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_inputs", IPC_GET_INPUTS) + pub fn get_inputs(&mut self) -> Result> { + self.run("get_inputs", IPC_GET_INPUTS) } /// Get the list of seats - pub fn get_seats(&mut self, socket: &mut UnixStream) -> Vec { - self.send_command(socket, "get_seats", IPC_GET_SEATS) + pub fn get_seats(&mut self) -> Result> { + self.run("get_seats", IPC_GET_SEATS) } - fn send_command(&mut self, socket: &mut UnixStream, payload: &str, payload_type: u32) -> Vec { - self.construct_packet(payload, payload_type); - socket.write_all(&self.buffer.as_slice()).unwrap(); + fn run(&mut self, payload: &str, payload_type: u32) -> Result + where + T: DeserializeOwned, + { + let buffer = match self.construct_packet(payload, payload_type) { + Ok(buffer) => buffer, + Err(e) => return Err(e), + }; + + match self.stream.write_all(&buffer.as_slice()) { + Ok(_) => {}, + Err(e) => return Err(e), + } let mut buffer = [0; 10000]; - let n = socket.read(&mut buffer).unwrap(); + let n = match self.stream.read(&mut buffer) { + Ok(n) => n, + Err(e) => return Err(e), + }; - self.response.write(&buffer[..n]).unwrap(); - self.response.to_owned() + match serde_json::from_slice::(&buffer[14..n]) { + Ok(result) => return Ok(result), + Err(e) => return Err(Error::new(ErrorKind::Other, e.to_string())), + } } - fn construct_packet(&mut self, payload: &str, payload_type: u32) { - self.buffer.write(self.magic_string.as_bytes()).unwrap(); - self.buffer.write(&payload.len().to_ne_bytes().as_slice()[..4]).unwrap(); - self.buffer.write(&payload_type.to_ne_bytes().as_slice()).unwrap(); - self.buffer.write(payload.as_bytes()).unwrap(); + fn construct_packet(&mut self, payload: &str, payload_type: u32) -> Result> { + let mut buffer = Vec::new(); + let length = payload.len().to_ne_bytes(); + let payload_type = payload_type.to_ne_bytes(); + + let bufs = &mut [ + IoSlice::new(self.magic_string.as_bytes()), + IoSlice::new(&length.as_slice()[..4]), + IoSlice::new(payload_type.as_slice()), + IoSlice::new(payload.as_bytes()), + ]; + + match buffer.write_vectored(bufs) { + Ok(_) => return Ok(buffer), + Err(e) => return Err(e), + } } } #[test] -fn send_command() { - let mut socket = UnixStream::connect(env!("SWAYSOCK")).unwrap(); - let response = I3msg::new().run_command(&mut socket, "reload"); - assert_eq!(response, vec![ - 0x69u8, 0x33u8 ,0x2Du8, 0x69u8, 0x70u8, 0x63u8, - 0x17u8, 0x00u8 ,0x00u8, 0x00u8, 0x00u8, 0x00u8, - 0x00u8, 0x00u8 ,0x5Bu8, 0x20u8, 0x7Bu8, 0x20u8, - 0x22u8, 0x73u8 ,0x75u8, 0x63u8, 0x63u8, 0x65u8, - 0x73u8, 0x73u8 ,0x22u8, 0x3Au8, 0x20u8, 0x74u8, - 0x72u8, 0x75u8 ,0x65u8, 0x20u8, 0x7Du8, 0x20u8, - 0x5Du8, - ]); +fn run_command() { + match I3msg::new(env!("SWAYSOCK")).run_command("reload") { + Ok(response) => assert_eq!(response.into_iter().nth(0).unwrap().success, true), + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_workspaces() { + match I3msg::new(env!("SWAYSOCK")).get_workspaces() { + Ok(response) => { + for workspace in response { + assert_eq!(workspace._type, "workspace"); + } + }, + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn subscribe() { + match I3msg::new(env!("SWAYSOCK")).subscribe("") { + Ok(response) => assert_eq!(response.success, false), + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_outputs() { + match I3msg::new(env!("SWAYSOCK")).get_outputs() { + Ok(response) => { + for output in response { + assert_eq!(output._type, "output"); + } + }, + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_tree() { + todo!() +} + +#[test] +fn get_marks() { + todo!() +} + +#[test] +fn get_bar_config() { + todo!() +} + +#[test] +fn get_version() { + match I3msg::new(env!("SWAYSOCK")).get_version() { + Ok(response) => assert_eq!(response.major, 1), + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_binding_modes() { + match I3msg::new(env!("SWAYSOCK")).get_binding_modes() { + Ok(response) => { + assert_eq!(response.into_iter().nth(0).unwrap(), "default") + }, + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_config() { + match I3msg::new(env!("SWAYSOCK")).get_config() { + Ok(_) => {}, + Err(e) => panic!("{}", e.to_string()) + } +} + +#[test] +fn send_tick() { + todo!() +} + +#[test] +fn sync() { + match I3msg::new(env!("SWAYSOCK")).sync() { + Ok(response) => assert_eq!(response.success, false), + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_binding_state() { + todo!() +} + +#[test] +fn get_inputs() { + match I3msg::new(env!("SWAYSOCK")).get_inputs() { + Ok(response) => { + for input in response { + if !(input._type.eq("keyboard") | input._type.eq("pointer")) { + panic!("invalid input: {}", input._type); + } + } + }, + Err(e) => panic!("{}", e.to_string()), + } +} + +#[test] +fn get_seats() { + todo!() } #[test] fn construct_packet() { - let mut i3 = I3msg::new(); - i3.construct_packet("reload", IPC_RUN_COMMAND); - assert_eq!(i3.buffer, vec![ - 0x69u8, 0x33u8, 0x2Du8, 0x69u8, - 0x70u8, 0x63u8, 0x06u8, 0x00u8, - 0x00u8, 0x00u8, 0x00u8, 0x00u8, - 0x00u8, 0x00u8, 0x72u8, 0x65u8, - 0x6Cu8, 0x6Fu8, 0x61u8, 0x64u8, - ]); + let mut i3 = I3msg::new(env!("SWAYSOCK")); + match i3.construct_packet("reload", IPC_RUN_COMMAND) { + Ok(buffer) => { + assert_eq!(buffer, vec![ + 0x69, 0x33, 0x2D, 0x69, + 0x70, 0x63, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x72, 0x65, + 0x6C, 0x6F, 0x61, 0x64, + ]); + }, + Err(e) => panic!("{}", e.to_string()), + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 67d02a3..8030b9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ -use std::os::unix::net::UnixStream; use std::env; fn main() -> std::io::Result<()> { - match UnixStream::connect(env!("SWAYSOCK")) { - Ok(mut sock) => { - let mut i3 = sway_ipc::I3msg::new(); - for ws in i3.get_workspaces(&mut sock) { + let mut i3 = sway_ipc::I3msg::new(env!("SWAYSOCK")); + match i3.get_workspaces() { + Ok(workspaces) => { + for ws in workspaces { for float in ws.floating_nodes { match float.app_id { Some(app_id) => println!("{}", app_id), @@ -15,8 +14,18 @@ fn main() -> std::io::Result<()> { } }, Err(e) => { - println!("Couldn't connect: {e:?}"); + eprintln!("error: {e:?}"); } - }; + } + match i3.get_outputs() { + Ok(outputs) => { + for output in outputs { + println!("output: {}", output.name); + } + }, + Err(e) => { + eprintln!("error: {e:?}"); + } + } Ok(()) } \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 0b83080..1637f53 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,87 +1,173 @@ -pub mod workspace { - use serde::{Deserialize, Serialize}; +use serde::{Serialize, Deserialize}; - #[derive(Serialize, Deserialize)] - pub struct Workspace { - pub id: i32, - #[serde(rename = "type")] - pub _type: String, - pub orientation: String, - pub percent: Option, - pub urgent: bool, - // todo marks - pub layout: String, - pub border: String, - pub current_border_width: i32, - pub rect: Rect, - pub deco_rect: Rect, - pub window_rect: Rect, - pub geometry: Rect, - pub name: String, - // todo window - // todo nodes - pub floating_nodes: Vec, - pub focus: Vec, - pub fullscreen_mode: i32, - pub sticky: bool, - pub num: i32, - pub output: String, - pub representation: String, - pub focused: bool, - pub visible: bool, - } - - #[derive(Serialize, Deserialize)] - pub struct FloatingNodes { - pub id: i32, - #[serde(rename = "type")] - pub _type: String, - pub orientation: String, - pub percent: f32, - pub urgent: bool, - // todo marks - pub focused: bool, - pub layout: String, - pub border: String, - pub current_border_width: i32, - pub rect: Rect, - pub deco_rect: Rect, - pub window_rect: Rect, - pub geometry: Rect, - pub name: String, - // todo nodes - pub focus: Vec, - pub fullscreen_mode: i32, - pub sticky: bool, - pub pid: i32, - pub app_id: Option, - pub visible: bool, - pub max_render_time: i32, - pub shell: String, - pub inhibit_idle: bool, - pub idle_inhibitors: IdleInhibitors, - pub window_properties: Option, - } - - #[derive(Serialize, Deserialize)] - pub struct IdleInhibitors { - user: String, - application: String, - } - - #[derive(Serialize, Deserialize)] - pub struct WindowProperties { - class: String, - instance: String, - title: String, - //todo transient_for - } - - #[derive(Serialize, Deserialize)] - pub struct Rect { - x: i32, - y: i32, - width: i32, - height: i32, - } +#[derive(Deserialize)] +pub struct Success { + pub success: bool, + pub parse_error: Option, + pub error: Option +} + +#[derive(Deserialize)] +pub struct Workspace { + pub id: i32, + #[serde(rename = "type")] + pub _type: String, + pub orientation: String, + pub percent: Option, + pub urgent: bool, + pub layout: String, + pub border: String, + pub current_border_width: i32, + pub rect: Rect, + pub deco_rect: Rect, + pub window_rect: Rect, + pub geometry: Rect, + pub name: String, + pub floating_nodes: Vec, + pub focus: Vec, + pub fullscreen_mode: i32, + pub sticky: bool, + pub num: i32, + pub output: String, + pub representation: String, + pub focused: bool, + pub visible: bool, +} + +#[derive(Deserialize)] +pub struct Output { + pub id: i32, + #[serde(rename = "type")] + pub _type: String, + pub orientation: String, + pub percent: Option, + pub urgent: bool, + pub layout: String, + pub border: String, + pub current_border_width: i32, + pub rect: Rect, + pub deco_rect: Rect, + pub window_rect: Rect, + pub geometry: Rect, + pub name: String, + pub floating_nodes: Vec, + pub focus: Vec, + pub fullscreen_mode: i32, + pub sticky: bool, + pub active: bool, + pub dpms: bool, + pub primary: bool, + pub make: String, + pub model: String, + pub serial: String, + pub scale: f32, + pub scale_filter: String, + pub transform: String, + pub adaptive_sync_status: String, + pub current_workspace: String, + pub modes: Vec, + pub current_mode: Mode, + pub max_render_time: i32, + pub focused: bool, + pub subpixel_hinting: String +} + +#[derive(Deserialize)] +pub struct Config { + pub config: String +} + +#[derive(Deserialize)] +pub struct Mode { + pub width: i32, + pub height: i32, + pub refresh: i32 +} + +#[derive(Deserialize)] +pub struct Version { + pub human_readable: String, + pub variant: String, + pub major: i32, + pub minor: i32, + pub patch: i32, + pub loaded_config_file_name: String +} + +#[derive(Deserialize)] +pub struct Input { + pub identifier: String, + pub name: String, + pub vendor: i32, + pub product: i32, + #[serde(rename = "type")] + pub _type: String, + pub xkb_layout_names: Option>, + pub xkb_active_layout_index: Option, + pub xkb_active_layout_name: Option, + pub scroll_factor: Option, + pub libinput: LibInput, +} + +#[derive(Deserialize)] +pub struct LibInput { + pub send_events: String, + pub accel_speed: Option, + pub accel_profile: Option, + pub natural_scroll: Option, + pub left_handed: Option, + pub middle_emulation: Option, + pub scroll_method: Option, + pub scroll_button: Option, +} + +#[derive(Serialize, Deserialize)] +pub struct FloatingNodes { + pub id: i32, + #[serde(rename = "type")] + pub _type: String, + pub orientation: String, + pub percent: f32, + pub urgent: bool, + pub focused: bool, + pub layout: String, + pub border: String, + pub current_border_width: i32, + pub rect: Rect, + pub deco_rect: Rect, + pub window_rect: Rect, + pub geometry: Rect, + pub name: String, + pub focus: Vec, + pub fullscreen_mode: i32, + pub sticky: bool, + pub pid: i32, + pub app_id: Option, + pub visible: bool, + pub max_render_time: i32, + pub shell: String, + pub inhibit_idle: bool, + pub idle_inhibitors: IdleInhibitors, + pub window_properties: Option +} + +#[derive(Serialize, Deserialize)] +pub struct IdleInhibitors { + user: String, + application: String +} + +#[derive(Serialize, Deserialize)] +pub struct WindowProperties { + class: String, + instance: String, + title: String +} + +#[derive(Serialize, Deserialize)] +pub struct Rect { + x: i32, + y: i32, + width: i32, + height: i32 }