rewrite of functions for constructing and sending ipc messages to reduce code reuse when deserializing json

This commit is contained in:
Michael 2022-12-02 19:30:17 +00:00
parent f0ccfc3c5a
commit ae04447488
Signed by: michael
GPG Key ID: 523BD9EF68BDD44C
3 changed files with 390 additions and 163 deletions

View File

@ -2,7 +2,10 @@ mod types;
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::io::prelude::*; 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_RUN_COMMAND: u32 = 0;
pub const IPC_GET_WORKSPACES: u32 = 1; pub const IPC_GET_WORKSPACES: u32 = 1;
@ -22,140 +25,269 @@ pub const IPC_GET_SEATS: u32 = 101;
pub struct I3msg { pub struct I3msg {
magic_string: &'static str, magic_string: &'static str,
buffer: Vec<u8>, stream: UnixStream,
response: Vec<u8>,
} }
impl I3msg { impl I3msg {
pub fn new() -> I3msg { pub fn new(socket: &str) -> I3msg {
let stream = UnixStream::connect(socket).unwrap();
return I3msg { return I3msg {
magic_string: "i3-ipc", magic_string: "i3-ipc",
buffer: Vec::new(), stream: stream
response: Vec::new(),
} }
} }
/// Runs the payload as sway commands /// Runs the payload as sway commands
pub fn run_command(&mut self, socket: &mut UnixStream, payload: &str) -> Vec<u8> { pub fn run_command(&mut self, payload: &str) -> Result<Vec<Success>> {
self.send_command(socket, payload, IPC_RUN_COMMAND) self.run(payload, IPC_RUN_COMMAND)
} }
/// Get the list of current workspaces /// Get the list of current workspaces
pub fn get_workspaces(&mut self, socket: &mut UnixStream) -> Vec<Workspace> { pub fn get_workspaces(&mut self) -> Result<Vec<Workspace>> {
let response = self.send_command(socket, "get_workspaces", IPC_GET_WORKSPACES); self.run("get_workspaces", IPC_GET_WORKSPACES)
serde_json::from_slice::<Vec<Workspace>>(&response[14..]).unwrap()
} }
/// Subscribe the IPC connection to the event listed in the payload /// Subscribe the IPC connection to the event listed in the payload
pub fn subscribe(&mut self, socket: &mut UnixStream, payload: &str) -> Vec<u8> { pub fn subscribe(&mut self, payload: &str) -> Result<Success> {
self.send_command(socket, payload, IPC_SUBSCRIBE); self.run(payload, IPC_SUBSCRIBE)
todo!();
} }
/// Get the list of current outputs /// Get the list of current outputs
pub fn get_outputs(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_outputs(&mut self) -> Result<Vec<Output>> {
self.send_command(socket, "get_outputs", IPC_GET_OUTPUTS) self.run("get_outputs", IPC_GET_OUTPUTS)
} }
/// Get the node layout tree /// Get the node layout tree
pub fn get_tree(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_tree(&mut self) -> Result<Vec<u8>> {
self.send_command(socket, "get_tree", IPC_GET_TREE) self.run("get_tree", IPC_GET_TREE)
} }
/// Get the names of all the marks currently set /// Get the names of all the marks currently set
pub fn get_marks(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_marks(&mut self) -> Result<Vec<String>> {
self.send_command(socket, "get_marks", IPC_GET_MARKS) self.run("get_marks", IPC_GET_MARKS)
} }
/// Get the specified bar config or a list of bar config names /// Get the specified bar config or a list of bar config names
pub fn get_bar_config(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_bar_config(&mut self, payload: &str) -> Result<Vec<u8>> {
self.send_command(socket, "get_bar_config", IPC_GET_BAR_CONFIG) self.run(payload, IPC_GET_BAR_CONFIG)
} }
/// Get the version of sway that owns the IPC socket /// Get the version of sway that owns the IPC socket
pub fn get_version(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_version(&mut self) -> Result<Version> {
self.send_command(socket, "get_version", IPC_GET_VERSION) self.run("get_version", IPC_GET_VERSION)
} }
/// Get the list of binding mode names /// Get the list of binding mode names
pub fn get_binding_modes(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_binding_modes(&mut self) -> Result<Vec<String>> {
self.send_command(socket, "get_binding_modes", IPC_GET_BINDING_MODES) self.run("get_binding_modes", IPC_GET_BINDING_MODES)
} }
/// Returns the config that was last loaded /// Returns the config that was last loaded
pub fn get_config(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_config(&mut self) -> Result<Config> {
self.send_command(socket, "get_config", IPC_GET_CONFIG) self.run("get_config", IPC_GET_CONFIG)
} }
/// Sends a tick event with specified payload /// Sends a tick event with specified payload
pub fn send_tick(&mut self, socket: &mut UnixStream, payload: &str) -> Vec<u8> { pub fn send_tick(&mut self, payload: &str) -> Result<Success> {
self.send_command(socket, payload, IPC_SEND_TICK); self.run(payload, IPC_SEND_TICK)
todo!()
} }
/// Replies failure object for i3 compatibility /// Replies failure object for i3 compatibility
pub fn sync(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn sync(&mut self) -> Result<Success> {
self.send_command(socket, "sync", IPC_SYNC) self.run("sync", IPC_SYNC)
} }
/// Request the current binding state, e.g. the currently active binding mode name /// Request the current binding state, e.g. the currently active binding mode name
pub fn get_binding_state(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_binding_state(&mut self) -> Result<Vec<u8>> {
self.send_command(socket, "get_binding_state", IPC_GET_BINDING_STATE) self.run("get_binding_state", IPC_GET_BINDING_STATE)
} }
/// Get the list of input devices /// Get the list of input devices
pub fn get_inputs(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_inputs(&mut self) -> Result<Vec<Input>> {
self.send_command(socket, "get_inputs", IPC_GET_INPUTS) self.run("get_inputs", IPC_GET_INPUTS)
} }
/// Get the list of seats /// Get the list of seats
pub fn get_seats(&mut self, socket: &mut UnixStream) -> Vec<u8> { pub fn get_seats(&mut self) -> Result<Vec<u8>> {
self.send_command(socket, "get_seats", IPC_GET_SEATS) self.run("get_seats", IPC_GET_SEATS)
} }
fn send_command(&mut self, socket: &mut UnixStream, payload: &str, payload_type: u32) -> Vec<u8> { fn run<T>(&mut self, payload: &str, payload_type: u32) -> Result<T>
self.construct_packet(payload, payload_type); where
socket.write_all(&self.buffer.as_slice()).unwrap(); 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 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(); match serde_json::from_slice::<T>(&buffer[14..n]) {
self.response.to_owned() 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) { fn construct_packet(&mut self, payload: &str, payload_type: u32) -> Result<Vec<u8>> {
self.buffer.write(self.magic_string.as_bytes()).unwrap(); let mut buffer = Vec::new();
self.buffer.write(&payload.len().to_ne_bytes().as_slice()[..4]).unwrap(); let length = payload.len().to_ne_bytes();
self.buffer.write(&payload_type.to_ne_bytes().as_slice()).unwrap(); let payload_type = payload_type.to_ne_bytes();
self.buffer.write(payload.as_bytes()).unwrap();
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] #[test]
fn send_command() { fn run_command() {
let mut socket = UnixStream::connect(env!("SWAYSOCK")).unwrap(); match I3msg::new(env!("SWAYSOCK")).run_command("reload") {
let response = I3msg::new().run_command(&mut socket, "reload"); Ok(response) => assert_eq!(response.into_iter().nth(0).unwrap().success, true),
assert_eq!(response, vec![ Err(e) => panic!("{}", e.to_string()),
0x69u8, 0x33u8 ,0x2Du8, 0x69u8, 0x70u8, 0x63u8, }
0x17u8, 0x00u8 ,0x00u8, 0x00u8, 0x00u8, 0x00u8, }
0x00u8, 0x00u8 ,0x5Bu8, 0x20u8, 0x7Bu8, 0x20u8,
0x22u8, 0x73u8 ,0x75u8, 0x63u8, 0x63u8, 0x65u8, #[test]
0x73u8, 0x73u8 ,0x22u8, 0x3Au8, 0x20u8, 0x74u8, fn get_workspaces() {
0x72u8, 0x75u8 ,0x65u8, 0x20u8, 0x7Du8, 0x20u8, match I3msg::new(env!("SWAYSOCK")).get_workspaces() {
0x5Du8, 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] #[test]
fn construct_packet() { fn construct_packet() {
let mut i3 = I3msg::new(); let mut i3 = I3msg::new(env!("SWAYSOCK"));
i3.construct_packet("reload", IPC_RUN_COMMAND); match i3.construct_packet("reload", IPC_RUN_COMMAND) {
assert_eq!(i3.buffer, vec![ Ok(buffer) => {
0x69u8, 0x33u8, 0x2Du8, 0x69u8, assert_eq!(buffer, vec![
0x70u8, 0x63u8, 0x06u8, 0x00u8, 0x69, 0x33, 0x2D, 0x69,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x70, 0x63, 0x06, 0x00,
0x00u8, 0x00u8, 0x72u8, 0x65u8, 0x00, 0x00, 0x00, 0x00,
0x6Cu8, 0x6Fu8, 0x61u8, 0x64u8, 0x00, 0x00, 0x72, 0x65,
0x6C, 0x6F, 0x61, 0x64,
]); ]);
},
Err(e) => panic!("{}", e.to_string()),
}
} }

View File

@ -1,11 +1,10 @@
use std::os::unix::net::UnixStream;
use std::env; use std::env;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
match UnixStream::connect(env!("SWAYSOCK")) { let mut i3 = sway_ipc::I3msg::new(env!("SWAYSOCK"));
Ok(mut sock) => { match i3.get_workspaces() {
let mut i3 = sway_ipc::I3msg::new(); Ok(workspaces) => {
for ws in i3.get_workspaces(&mut sock) { for ws in workspaces {
for float in ws.floating_nodes { for float in ws.floating_nodes {
match float.app_id { match float.app_id {
Some(app_id) => println!("{}", app_id), Some(app_id) => println!("{}", app_id),
@ -15,8 +14,18 @@ fn main() -> std::io::Result<()> {
} }
}, },
Err(e) => { 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(()) Ok(())
} }

View File

@ -1,7 +1,13 @@
pub mod workspace { use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
pub struct Success {
pub success: bool,
pub parse_error: Option<bool>,
pub error: Option<String>
}
#[derive(Deserialize)]
pub struct Workspace { pub struct Workspace {
pub id: i32, pub id: i32,
#[serde(rename = "type")] #[serde(rename = "type")]
@ -9,7 +15,6 @@ pub mod workspace {
pub orientation: String, pub orientation: String,
pub percent: Option<f32>, pub percent: Option<f32>,
pub urgent: bool, pub urgent: bool,
// todo marks
pub layout: String, pub layout: String,
pub border: String, pub border: String,
pub current_border_width: i32, pub current_border_width: i32,
@ -18,8 +23,6 @@ pub mod workspace {
pub window_rect: Rect, pub window_rect: Rect,
pub geometry: Rect, pub geometry: Rect,
pub name: String, pub name: String,
// todo window
// todo nodes
pub floating_nodes: Vec<FloatingNodes>, pub floating_nodes: Vec<FloatingNodes>,
pub focus: Vec<i32>, pub focus: Vec<i32>,
pub fullscreen_mode: i32, pub fullscreen_mode: i32,
@ -31,6 +34,93 @@ pub mod workspace {
pub visible: bool, pub visible: bool,
} }
#[derive(Deserialize)]
pub struct Output {
pub id: i32,
#[serde(rename = "type")]
pub _type: String,
pub orientation: String,
pub percent: Option<f32>,
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<FloatingNodes>,
pub focus: Vec<i32>,
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<Mode>,
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<Vec<String>>,
pub xkb_active_layout_index: Option<i32>,
pub xkb_active_layout_name: Option<String>,
pub scroll_factor: Option<f32>,
pub libinput: LibInput,
}
#[derive(Deserialize)]
pub struct LibInput {
pub send_events: String,
pub accel_speed: Option<f32>,
pub accel_profile: Option<String>,
pub natural_scroll: Option<String>,
pub left_handed: Option<String>,
pub middle_emulation: Option<String>,
pub scroll_method: Option<String>,
pub scroll_button: Option<i32>,
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct FloatingNodes { pub struct FloatingNodes {
pub id: i32, pub id: i32,
@ -39,7 +129,6 @@ pub mod workspace {
pub orientation: String, pub orientation: String,
pub percent: f32, pub percent: f32,
pub urgent: bool, pub urgent: bool,
// todo marks
pub focused: bool, pub focused: bool,
pub layout: String, pub layout: String,
pub border: String, pub border: String,
@ -49,7 +138,6 @@ pub mod workspace {
pub window_rect: Rect, pub window_rect: Rect,
pub geometry: Rect, pub geometry: Rect,
pub name: String, pub name: String,
// todo nodes
pub focus: Vec<i32>, pub focus: Vec<i32>,
pub fullscreen_mode: i32, pub fullscreen_mode: i32,
pub sticky: bool, pub sticky: bool,
@ -60,21 +148,20 @@ pub mod workspace {
pub shell: String, pub shell: String,
pub inhibit_idle: bool, pub inhibit_idle: bool,
pub idle_inhibitors: IdleInhibitors, pub idle_inhibitors: IdleInhibitors,
pub window_properties: Option<WindowProperties>, pub window_properties: Option<WindowProperties>
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct IdleInhibitors { pub struct IdleInhibitors {
user: String, user: String,
application: String, application: String
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct WindowProperties { pub struct WindowProperties {
class: String, class: String,
instance: String, instance: String,
title: String, title: String
//todo transient_for
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -82,6 +169,5 @@ pub mod workspace {
x: i32, x: i32,
y: i32, y: i32,
width: i32, width: i32,
height: i32, height: i32
}
} }