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::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<u8>,
response: Vec<u8>,
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<u8> {
self.send_command(socket, payload, IPC_RUN_COMMAND)
pub fn run_command(&mut self, payload: &str) -> Result<Vec<Success>> {
self.run(payload, IPC_RUN_COMMAND)
}
/// Get the list of current workspaces
pub fn get_workspaces(&mut self, socket: &mut UnixStream) -> Vec<Workspace> {
let response = self.send_command(socket, "get_workspaces", IPC_GET_WORKSPACES);
serde_json::from_slice::<Vec<Workspace>>(&response[14..]).unwrap()
pub fn get_workspaces(&mut self) -> Result<Vec<Workspace>> {
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<u8> {
self.send_command(socket, payload, IPC_SUBSCRIBE);
todo!();
pub fn subscribe(&mut self, payload: &str) -> Result<Success> {
self.run(payload, IPC_SUBSCRIBE)
}
/// Get the list of current outputs
pub fn get_outputs(&mut self, socket: &mut UnixStream) -> Vec<u8> {
self.send_command(socket, "get_outputs", IPC_GET_OUTPUTS)
pub fn get_outputs(&mut self) -> Result<Vec<Output>> {
self.run("get_outputs", IPC_GET_OUTPUTS)
}
/// Get the node layout tree
pub fn get_tree(&mut self, socket: &mut UnixStream) -> Vec<u8> {
self.send_command(socket, "get_tree", IPC_GET_TREE)
pub fn get_tree(&mut self) -> Result<Vec<u8>> {
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<u8> {
self.send_command(socket, "get_marks", IPC_GET_MARKS)
pub fn get_marks(&mut self) -> Result<Vec<String>> {
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<u8> {
self.send_command(socket, "get_bar_config", IPC_GET_BAR_CONFIG)
pub fn get_bar_config(&mut self, payload: &str) -> Result<Vec<u8>> {
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<u8> {
self.send_command(socket, "get_version", IPC_GET_VERSION)
pub fn get_version(&mut self) -> Result<Version> {
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<u8> {
self.send_command(socket, "get_binding_modes", IPC_GET_BINDING_MODES)
pub fn get_binding_modes(&mut self) -> Result<Vec<String>> {
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<u8> {
self.send_command(socket, "get_config", IPC_GET_CONFIG)
pub fn get_config(&mut self) -> Result<Config> {
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<u8> {
self.send_command(socket, payload, IPC_SEND_TICK);
todo!()
pub fn send_tick(&mut self, payload: &str) -> Result<Success> {
self.run(payload, IPC_SEND_TICK)
}
/// Replies failure object for i3 compatibility
pub fn sync(&mut self, socket: &mut UnixStream) -> Vec<u8> {
self.send_command(socket, "sync", IPC_SYNC)
pub fn sync(&mut self) -> Result<Success> {
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<u8> {
self.send_command(socket, "get_binding_state", IPC_GET_BINDING_STATE)
pub fn get_binding_state(&mut self) -> Result<Vec<u8>> {
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<u8> {
self.send_command(socket, "get_inputs", IPC_GET_INPUTS)
pub fn get_inputs(&mut self) -> Result<Vec<Input>> {
self.run("get_inputs", IPC_GET_INPUTS)
}
/// Get the list of seats
pub fn get_seats(&mut self, socket: &mut UnixStream) -> Vec<u8> {
self.send_command(socket, "get_seats", IPC_GET_SEATS)
pub fn get_seats(&mut self) -> Result<Vec<u8>> {
self.run("get_seats", IPC_GET_SEATS)
}
fn send_command(&mut self, socket: &mut UnixStream, payload: &str, payload_type: u32) -> Vec<u8> {
self.construct_packet(payload, payload_type);
socket.write_all(&self.buffer.as_slice()).unwrap();
fn run<T>(&mut self, payload: &str, payload_type: u32) -> Result<T>
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::<T>(&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<Vec<u8>> {
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()),
}
}

View File

@ -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(())
}

View File

@ -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<f32>,
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<FloatingNodes>,
pub focus: Vec<i32>,
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<i32>,
pub fullscreen_mode: i32,
pub sticky: bool,
pub pid: i32,
pub app_id: Option<String>,
pub visible: bool,
pub max_render_time: i32,
pub shell: String,
pub inhibit_idle: bool,
pub idle_inhibitors: IdleInhibitors,
pub window_properties: Option<WindowProperties>,
}
#[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<bool>,
pub error: Option<String>
}
#[derive(Deserialize)]
pub struct Workspace {
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 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<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)]
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<i32>,
pub fullscreen_mode: i32,
pub sticky: bool,
pub pid: i32,
pub app_id: Option<String>,
pub visible: bool,
pub max_render_time: i32,
pub shell: String,
pub inhibit_idle: bool,
pub idle_inhibitors: IdleInhibitors,
pub window_properties: Option<WindowProperties>
}
#[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
}