From 90ec4a2566a6b1d1553abee4ea7a5f71a3bae53b Mon Sep 17 00:00:00 2001 From: Skia Date: Tue, 14 Nov 2023 09:44:33 +0100 Subject: [PATCH] Rework everything to have a global SwaySome object with methods in it This allows easily keeping state across the various function calls. --- src/main.rs | 1072 ++++++++++++++++++++++++++------------------------- 1 file changed, 551 insertions(+), 521 deletions(-) diff --git a/src/main.rs b/src/main.rs index e4f5e6b..04f24aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate serde_json; use serde::{Deserialize, Serialize}; use clap::{Args, Parser, Subcommand}; +use std::cell::Cell; use std::env; use std::io::Cursor; use std::io::{Read, Write}; @@ -78,105 +79,12 @@ struct Index { index: usize, } -fn get_stream() -> UnixStream { - for socket_var in ["SWAYSOCK", "I3SOCK"] { - let socket_path = match env::var(socket_var) { - Ok(val) => val, - Err(_e) => { - eprintln!("{} not found in environment", socket_var); - continue; - } - }; - - let socket = Path::new(&socket_path); - - match UnixStream::connect(&socket) { - Err(_) => { - eprintln!( - "counldn't connect to socket '{}' found in ${}", - socket_path, socket_var - ); - continue; - } - Ok(stream) => { - eprintln!( - "successful connection to socket '{}' found in ${}", - socket_path, socket_var - ); - return stream; - } - } - } - panic!("couldn't find any i3/sway socket") -} - -fn send_msg(mut stream: &UnixStream, msg_type: u32, payload: &str) { - let payload_length = payload.len() as u32; - - let mut msg_prefix: [u8; 6 * mem::size_of::() + 2 * mem::size_of::()] = - *b"i3-ipc00000000"; - - msg_prefix[6..] - .as_mut() - .write_u32::(payload_length) - .expect("Unable to write"); - - msg_prefix[10..] - .as_mut() - .write_u32::(msg_type) - .expect("Unable to write"); - - let mut msg: Vec = msg_prefix[..].to_vec(); - msg.extend(payload.as_bytes()); - - if stream.write_all(&msg[..]).is_err() { - panic!("couldn't send message"); - } -} - -fn send_command(stream: &UnixStream, command: &str) { - eprint!("Sending command: '{}' - ", &command); - send_msg(stream, RUN_COMMAND, command); - check_success(stream); -} - -fn read_msg(mut stream: &UnixStream) -> Result { - let mut response_header: [u8; 14] = *b"uninitialized."; - stream.read_exact(&mut response_header).unwrap(); - - if &response_header[0..6] == b"i3-ipc" { - let mut v = Cursor::new(vec![ - response_header[6], - response_header[7], - response_header[8], - response_header[9], - ]); - let payload_length = v.read_u32::().unwrap(); - - let mut payload = vec![0; payload_length as usize]; - stream.read_exact(&mut payload[..]).unwrap(); - let payload_str = String::from_utf8(payload).unwrap(); - Ok(payload_str) - } else { - eprint!("Not an i3-icp packet, emptying the buffer: "); - let mut v = vec![]; - stream.read_to_end(&mut v).unwrap(); - eprintln!("{:?}", v); - Err("Unable to read i3-ipc packet") - } -} - -fn check_success(stream: &UnixStream) { - match read_msg(stream) { - Ok(msg) => { - let r: Vec = serde_json::from_str(&msg).unwrap(); - match r[0]["success"] { - serde_json::Value::Bool(true) => eprintln!("Command successful"), - _ => panic!("Command failed: {:#?}", r), - } - } - Err(_) => panic!("Unable to read response"), - }; +struct SwaySome { + socket: Cell>, + outputs: Vec, + // current_output: Output, + workspaces: Vec, + // current_workspace: Workspace, } #[derive(Serialize, Deserialize, Debug)] @@ -188,21 +96,6 @@ struct Output { active: bool, } -fn get_outputs(stream: &UnixStream) -> Vec { - send_msg(stream, GET_OUTPUTS, ""); - let o = match read_msg(stream) { - Ok(msg) => msg, - Err(_) => panic!("Unable to get outputs"), - }; - let mut outputs: Vec = serde_json::from_str::>(&o) - .unwrap() - .into_iter() - .filter(|x| x.active) - .collect(); - outputs.sort_by(|x, y| x.name.cmp(&y.name)); // sort_by_key doesn't work here (https://stackoverflow.com/a/47126516) - outputs -} - #[derive(Serialize, Deserialize, Debug)] struct Workspace { num: usize, @@ -210,460 +103,597 @@ struct Workspace { visible: bool, } -fn get_workspaces(stream: &UnixStream) -> Vec { - send_msg(stream, GET_WORKSPACES, ""); - let ws = match read_msg(stream) { - Ok(msg) => msg, - Err(_) => panic!("Unable to get current workspace"), - }; - let mut workspaces: Vec = serde_json::from_str(&ws).unwrap(); - workspaces.sort_by_key(|x| x.num); - workspaces -} - -fn get_current_output_index(stream: &UnixStream) -> usize { - let outputs = get_outputs(stream); - - match outputs.iter().position(|x| x.focused) { - Some(i) => i, - None => panic!("WTF! No focused output???"), +impl SwaySome { + fn new() -> SwaySome { + let mut swaysome = SwaySome { + socket: Cell::new(Some(SwaySome::get_stream())), + outputs: vec![], + workspaces: vec![], + }; + swaysome.outputs = swaysome.get_outputs(); + swaysome.workspaces = swaysome.get_workspaces(); + swaysome } -} -fn get_current_output_name(stream: &UnixStream) -> String { - let outputs = get_outputs(stream); + fn get_stream() -> UnixStream { + for socket_var in ["SWAYSOCK", "I3SOCK"] { + let socket_path = match env::var(socket_var) { + Ok(val) => val, + Err(_e) => { + eprintln!("{} not found in environment", socket_var); + continue; + } + }; - let focused_output_index = match outputs.iter().find(|x| x.focused) { - Some(i) => i.name.as_str(), - None => panic!("WTF! No focused output???"), - }; + let socket = Path::new(&socket_path); - focused_output_index.to_string() -} - -fn get_current_workspace(stream: &UnixStream) -> Workspace { - let outputs = get_outputs(stream); - let workspaces = get_workspaces(stream); - workspaces - .into_iter() - .find(|w| w.visible && outputs.iter().find(|o| o.name == w.output).unwrap().focused) - .unwrap() -} - -fn move_container_to_workspace(stream: &UnixStream, workspace_index: usize) { - if workspace_index < MAX_GROUP_WS { - move_container_to_workspace_relative(stream, workspace_index); - } else { - move_container_to_workspace_absolute(stream, workspace_index); - } -} - -fn move_container_to_workspace_group(stream: &UnixStream, target_group: usize) { - let current_workspace_index = get_current_workspace(stream).num; - let current_workspace_index_relative = (current_workspace_index % MAX_GROUP_WS) as usize; - move_container_to_workspace_absolute( - stream, - current_workspace_index_relative + target_group * MAX_GROUP_WS, - ); -} - -fn move_container_to_workspace_absolute(stream: &UnixStream, workspace_index: usize) { - let group_index = (workspace_index / MAX_GROUP_WS) as usize; - let outputs = get_outputs(stream); - let workspaces = get_workspaces(stream); - let full_ws_name = format!( - "{}", - group_index * MAX_GROUP_WS + workspace_index % MAX_GROUP_WS - ); - - // If the workspace already exists - match workspaces.iter().find(|w| w.num == workspace_index) { - Some(_) => { - let mut focus_cmd: String = "move container to workspace number ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); + match UnixStream::connect(&socket) { + Err(_) => { + eprintln!( + "counldn't connect to socket '{}' found in ${}", + socket_path, socket_var + ); + continue; + } + Ok(stream) => { + eprintln!( + "successful connection to socket '{}' found in ${}", + socket_path, socket_var + ); + return stream; + } + } } - None => { - let target_group = workspace_index / MAX_GROUP_WS; - let target_screen_index = match workspaces - .iter() - .find(|w| w.num / MAX_GROUP_WS == target_group) - { - // If other workspaces on the same group exists - Some(other_workspace) => Some( - outputs + panic!("couldn't find any i3/sway socket") + } + + fn send_msg(&self, msg_type: u32, payload: &str) { + let payload_length = payload.len() as u32; + + let mut msg_prefix: [u8; 6 * mem::size_of::() + 2 * mem::size_of::()] = + *b"i3-ipc00000000"; + + msg_prefix[6..] + .as_mut() + .write_u32::(payload_length) + .expect("Unable to write"); + + msg_prefix[10..] + .as_mut() + .write_u32::(msg_type) + .expect("Unable to write"); + + let mut msg: Vec = msg_prefix[..].to_vec(); + msg.extend(payload.as_bytes()); + + let mut socket = self + .socket + .take() + .expect("Unexisting socket, there probably is a logic error"); + + if socket.write_all(&msg[..]).is_err() { + panic!("couldn't send message"); + } + self.socket.set(Some(socket)); + } + + fn send_command(&self, command: &str) { + eprint!("Sending command: '{}' - ", &command); + self.send_msg(RUN_COMMAND, command); + self.check_success(); + } + + fn read_msg(&self) -> Result { + let mut response_header: [u8; 14] = *b"uninitialized."; + let mut socket = self + .socket + .take() + .expect("Unexisting socket, there probably is a logic error"); + socket.read_exact(&mut response_header).unwrap(); + + if &response_header[0..6] == b"i3-ipc" { + let mut v = Cursor::new(vec![ + response_header[6], + response_header[7], + response_header[8], + response_header[9], + ]); + let payload_length = v.read_u32::().unwrap(); + + let mut payload = vec![0; payload_length as usize]; + socket.read_exact(&mut payload[..]).unwrap(); + let payload_str = String::from_utf8(payload).unwrap(); + self.socket.set(Some(socket)); + Ok(payload_str) + } else { + eprint!("Not an i3-icp packet, emptying the buffer: "); + let mut v = vec![]; + socket.read_to_end(&mut v).unwrap(); + eprintln!("{:?}", v); + self.socket.set(Some(socket)); + Err("Unable to read i3-ipc packet") + } + } + + fn check_success(&self) { + match self.read_msg() { + Ok(msg) => { + let r: Vec = serde_json::from_str(&msg).unwrap(); + match r[0]["success"] { + serde_json::Value::Bool(true) => eprintln!("Command successful"), + _ => panic!("Command failed: {:#?}", r), + } + } + Err(_) => panic!("Unable to read response"), + }; + } + + fn get_outputs(&self) -> Vec { + self.send_msg(GET_OUTPUTS, ""); + let o = match self.read_msg() { + Ok(msg) => msg, + Err(_) => panic!("Unable to get outputs"), + }; + let mut outputs: Vec = serde_json::from_str::>(&o) + .unwrap() + .into_iter() + .filter(|x| x.active) + .collect(); + outputs.sort_by(|x, y| x.name.cmp(&y.name)); // sort_by_key doesn't work here (https://stackoverflow.com/a/47126516) + outputs + } + + fn get_workspaces(&self) -> Vec { + self.send_msg(GET_WORKSPACES, ""); + let ws = match self.read_msg() { + Ok(msg) => msg, + Err(_) => panic!("Unable to get current workspace"), + }; + let mut workspaces: Vec = serde_json::from_str(&ws).unwrap(); + workspaces.sort_by_key(|x| x.num); + workspaces + } + + fn get_current_output_index(&self) -> usize { + match self.outputs.iter().position(|x| x.focused) { + Some(i) => i, + None => panic!("WTF! No focused output???"), + } + } + + fn get_current_output_name(&self) -> String { + let focused_output_index = match self.outputs.iter().find(|x| x.focused) { + Some(i) => i.name.as_str(), + None => panic!("WTF! No focused output???"), + }; + + focused_output_index.to_string() + } + + fn get_current_workspace_index(&self) -> usize { + self.workspaces + .iter() + .find(|w| { + w.visible + && self + .outputs .iter() - .enumerate() - .find(|i| i.1.name == other_workspace.output) + .find(|o| o.name == w.output) .unwrap() - .0, - ), - None => { - // Or if the targeted output is currently connected - if group_index < outputs.len() { - Some(group_index) - } else { - None - } - } - }; - - match target_screen_index { - Some(target_screen_index) => { - let target_output = &outputs[target_screen_index]; - - let current_output_name = get_current_output_name(stream); - - if target_output.name == current_output_name { - let mut focus_cmd: String = "move container to workspace ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); - } else { - // If we have to send it to another screen - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(&target_output.name); - send_command(stream, &focus_cmd); - - let focused_workspace_index = get_current_workspace(stream).num; - - let mut focus_cmd: String = "workspace ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); - - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(¤t_output_name); - send_command(stream, &focus_cmd); - - let mut focus_cmd: String = "move container to workspace ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); - - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(&target_output.name); - send_command(stream, &focus_cmd); - - let mut focus_cmd: String = "workspace ".to_string(); - focus_cmd.push_str(&focused_workspace_index.to_string()); - send_command(stream, &focus_cmd); - - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(¤t_output_name); - send_command(stream, &focus_cmd); - } - } - None => { - // Else, we send the container on the current output - let mut focus_cmd: String = "move container to workspace ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); - } - }; - } - } -} - -fn move_container_to_workspace_relative(stream: &UnixStream, workspace_index: usize) { - let current_workspace_index: usize = get_current_workspace(stream).num; - let focused_output_index = current_workspace_index / MAX_GROUP_WS; - - let mut cmd: String = "move container to workspace number ".to_string(); - let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index); - cmd.push_str(&full_ws_name); - send_command(stream, &cmd); -} - -fn focus_to_workspace(stream: &UnixStream, workspace_index: usize) { - if workspace_index < MAX_GROUP_WS { - focus_to_workspace_relative(stream, workspace_index); - } else { - focus_to_workspace_absolute(stream, workspace_index); - } -} - -fn focus_to_workspace_absolute(stream: &UnixStream, workspace_index: usize) { - let output_index = (workspace_index / MAX_GROUP_WS) as usize; - let outputs = get_outputs(stream); - let workspaces = get_workspaces(stream); - - // If the workspace already exists - match workspaces.iter().find(|w| w.num == workspace_index) { - Some(_) => { - let mut focus_cmd: String = "workspace number ".to_string(); - focus_cmd.push_str(&workspace_index.to_string()); - send_command(stream, &focus_cmd); - } - None => { - let target_group = workspace_index / MAX_GROUP_WS; - let target_screen_index = match workspaces - .iter() - .find(|w| w.num / MAX_GROUP_WS == target_group) - { - // If other workspaces on the same group exists - Some(other_workspace) => Some( - outputs - .iter() - .enumerate() - .find(|i| i.1.name == other_workspace.output) - .unwrap() - .0, - ), - None => { - // Or if the targeted output is currently connected - if output_index < outputs.len() { - Some(output_index) - } else { - None - } - } - }; - - match target_screen_index { - // If we have to send it to another screen - Some(target_screen_index) => { - let target_output = &outputs[target_screen_index - 1]; - - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(&target_output.name); - send_command(stream, &focus_cmd); - } - None => {} - }; - // Then we focus the workspace - let mut focus_cmd: String = "workspace number ".to_string(); - focus_cmd.push_str(&workspace_index.to_string()); - send_command(stream, &focus_cmd); - } - } -} - -fn focus_to_workspace_relative(stream: &UnixStream, workspace_index: usize) { - let current_workspace_index: usize = get_current_workspace(stream).num; - let focused_output_index = current_workspace_index / MAX_GROUP_WS; - - let mut cmd: String = "workspace number ".to_string(); - let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index); - cmd.push_str(&full_ws_name); - send_command(stream, &cmd); -} - -fn focus_to_group(stream: &UnixStream, group_index: usize) { - let outputs = get_outputs(stream); - let workspaces = get_workspaces(stream); - - let current_workspace_index: usize = get_current_workspace(stream).num; - let target_workspace_relative_index = current_workspace_index % MAX_GROUP_WS; - - let target_workspace_index = group_index * MAX_GROUP_WS + target_workspace_relative_index; - let full_ws_name = format!( - "{}", - group_index * MAX_GROUP_WS + target_workspace_relative_index - ); - - // If the workspace already exists - match workspaces.iter().find(|w| w.num == target_workspace_index) { - Some(_) => { - let mut focus_cmd: String = "workspace number ".to_string(); - focus_cmd.push_str(&full_ws_name); - send_command(stream, &focus_cmd); - } - None => { - let target_screen_index = match workspaces - .iter() - .find(|w| w.num / MAX_GROUP_WS == group_index) - { - // If other workspaces on the same group exists - Some(other_workspace) => Some( - outputs - .iter() - .enumerate() - .find(|i| i.1.name == other_workspace.output) - .unwrap() - .0 - + 1, - ), - None => { - // Or if the targeted output is currently connected - if group_index > 0 && group_index <= outputs.len() { - Some(group_index) - } else { - None - } - } - }; - - match target_screen_index { - // If we have to send it to another screen - Some(target_screen_index) => { - let target_output = &outputs[target_screen_index - 1]; - - let mut focus_cmd: String = "focus output ".to_string(); - focus_cmd.push_str(&target_output.name); - send_command(stream, &focus_cmd); - } - None => {} - }; - // Then we focus the workspace - let mut focus_cmd: String = "workspace number ".to_string(); - focus_cmd.push_str(&target_workspace_index.to_string()); - send_command(stream, &focus_cmd); - } - } -} - -fn focus_all_outputs_to_workspace(stream: &UnixStream, workspace_index: usize) { - let current_output = get_current_output_name(stream); - - // Iterate on all outputs to focus on the given workspace - let outputs = get_outputs(stream); - for output in outputs.iter() { - let mut cmd: String = "focus output ".to_string(); - cmd.push_str(output.name.as_str()); - send_command(stream, &cmd); - - focus_to_workspace(stream, workspace_index); + .focused + }) + .unwrap() + .num } - // Get back to currently focused output - let mut cmd: String = "focus output ".to_string(); - cmd.push_str(¤t_output); - send_command(stream, &cmd); -} + fn move_container_to_workspace(&self, workspace_index: usize) { + if workspace_index < MAX_GROUP_WS { + self.move_container_to_workspace_relative(workspace_index); + } else { + self.move_container_to_workspace_absolute(workspace_index); + } + } -fn move_container_to_next_output(stream: &UnixStream) { - move_container_to_next_or_prev_output(stream, false); -} + fn move_container_to_workspace_group(&self, target_group: usize) { + let current_workspace_index = self.get_current_workspace_index(); + let current_workspace_index_relative = (current_workspace_index % MAX_GROUP_WS) as usize; + self.move_container_to_workspace_absolute( + current_workspace_index_relative + target_group * MAX_GROUP_WS, + ); + } -fn move_container_to_prev_output(stream: &UnixStream) { - move_container_to_next_or_prev_output(stream, true); -} - -fn move_container_to_next_or_prev_output(stream: &UnixStream, go_to_prev: bool) { - let outputs = get_outputs(stream); - let focused_output_index = get_current_output_index(stream); - - let target_output = if go_to_prev { - &outputs[(focused_output_index + outputs.len() - 1) % outputs.len()] - } else { - &outputs[(focused_output_index + 1) % outputs.len()] - }; - - let workspaces = get_workspaces(stream); - let target_workspace = workspaces - .iter() - .find(|x| x.output == target_output.name && x.visible) - .unwrap(); - let group_index = (target_workspace.num / MAX_GROUP_WS) as usize; - let full_ws_name = format!( - "{}", - group_index * MAX_GROUP_WS + target_workspace.num % MAX_GROUP_WS - ); - - // Move container to target workspace - let mut cmd: String = "move container to workspace number ".to_string(); - cmd.push_str(&full_ws_name); - send_command(stream, &cmd); - - // Focus that workspace to follow the container - let mut cmd: String = "workspace number ".to_string(); - cmd.push_str(&target_workspace.num.to_string()); - send_command(stream, &cmd); -} - -fn focus_container_to_next_group(stream: &UnixStream) { - focus_container_to_next_or_prev_group(stream, false); -} - -fn focus_container_to_prev_group(stream: &UnixStream) { - focus_container_to_next_or_prev_group(stream, true); -} - -fn focus_container_to_next_or_prev_group(stream: &UnixStream, go_to_prev: bool) { - let current_workspace_index: usize = get_current_workspace(stream).num; - let focused_group_index = current_workspace_index / MAX_GROUP_WS; - - if go_to_prev { - focus_to_group(stream, focused_group_index - 1); - } else { - focus_to_group(stream, focused_group_index + 1); - }; -} - -fn init_workspaces(stream: &UnixStream, workspace_index: usize) { - let outputs = get_outputs(stream); - - let cmd_prefix: String = "focus output ".to_string(); - for output in outputs.iter().rev() { - let mut cmd = cmd_prefix.clone(); - cmd.push_str(output.name.as_str()); - send_command(stream, &cmd); - - let mut cmd: String = "workspace number ".to_string(); + fn move_container_to_workspace_absolute(&self, workspace_index: usize) { + let group_index = (workspace_index / MAX_GROUP_WS) as usize; let full_ws_name = format!( "{}", - (get_current_output_index(stream) + 1) * MAX_GROUP_WS + workspace_index + group_index * MAX_GROUP_WS + workspace_index % MAX_GROUP_WS ); - cmd.push_str(&full_ws_name); - send_command(stream, &cmd); + + // If the workspace already exists + match self.workspaces.iter().find(|w| w.num == workspace_index) { + Some(_) => { + let mut focus_cmd: String = "move container to workspace number ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + } + None => { + let target_group = workspace_index / MAX_GROUP_WS; + let target_screen_index = match self + .workspaces + .iter() + .find(|w| w.num / MAX_GROUP_WS == target_group) + { + // If other workspaces on the same group exists + Some(other_workspace) => Some( + self.outputs + .iter() + .enumerate() + .find(|i| i.1.name == other_workspace.output) + .unwrap() + .0, + ), + None => { + // Or if the targeted output is currently connected + if group_index < self.outputs.len() { + Some(group_index) + } else { + None + } + } + }; + + match target_screen_index { + Some(target_screen_index) => { + let target_output = &self.outputs[target_screen_index]; + + let current_output_name = self.get_current_output_name(); + + if target_output.name == current_output_name { + let mut focus_cmd: String = "move container to workspace ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + } else { + // If we have to send it to another screen + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(&target_output.name); + self.send_command(&focus_cmd); + + let focused_workspace_index = self.get_current_workspace_index(); + + let mut focus_cmd: String = "workspace ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(¤t_output_name); + self.send_command(&focus_cmd); + + let mut focus_cmd: String = "move container to workspace ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(&target_output.name); + self.send_command(&focus_cmd); + + let mut focus_cmd: String = "workspace ".to_string(); + focus_cmd.push_str(&focused_workspace_index.to_string()); + self.send_command(&focus_cmd); + + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(¤t_output_name); + self.send_command(&focus_cmd); + } + } + None => { + // Else, we send the container on the current output + let mut focus_cmd: String = "move container to workspace ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + } + }; + } + } } -} -fn rearrange_workspaces(stream: &UnixStream) { - let outputs = get_outputs(stream); - let workspaces = get_workspaces(stream); + fn move_container_to_workspace_relative(&self, workspace_index: usize) { + let current_workspace_index: usize = self.get_current_workspace_index(); + let focused_output_index = current_workspace_index / MAX_GROUP_WS; - let focus_cmd_prefix: String = "workspace number ".to_string(); - let move_cmd_prefix: String = "move workspace to ".to_string(); - for workspace in workspaces.iter() { - let mut focus_cmd = focus_cmd_prefix.clone(); - focus_cmd.push_str(&workspace.num.to_string()); - send_command(stream, &focus_cmd); + let mut cmd: String = "move container to workspace number ".to_string(); + let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index); + cmd.push_str(&full_ws_name); + self.send_command(&cmd); + } - let group_index = workspace.num / MAX_GROUP_WS; - if group_index <= outputs.len() - 1 { - let mut move_cmd = move_cmd_prefix.clone(); - move_cmd.push_str(&outputs[group_index.max(1) - 1].name); - send_command(stream, &move_cmd); + fn focus_to_workspace(&self, workspace_index: usize) { + if workspace_index < MAX_GROUP_WS { + self.focus_to_workspace_relative(workspace_index); + } else { + self.focus_to_workspace_absolute(workspace_index); + } + } + + fn focus_to_workspace_absolute(&self, workspace_index: usize) { + let output_index = (workspace_index / MAX_GROUP_WS) as usize; + + // If the workspace already exists + match self.workspaces.iter().find(|w| w.num == workspace_index) { + Some(_) => { + let mut focus_cmd: String = "workspace number ".to_string(); + focus_cmd.push_str(&workspace_index.to_string()); + self.send_command(&focus_cmd); + } + None => { + let target_group = workspace_index / MAX_GROUP_WS; + let target_screen_index = match self + .workspaces + .iter() + .find(|w| w.num / MAX_GROUP_WS == target_group) + { + // If other workspaces on the same group exists + Some(other_workspace) => Some( + self.outputs + .iter() + .enumerate() + .find(|i| i.1.name == other_workspace.output) + .unwrap() + .0, + ), + None => { + // Or if the targeted output is currently connected + if output_index < self.outputs.len() { + Some(output_index) + } else { + None + } + } + }; + + match target_screen_index { + // If we have to send it to another screen + Some(target_screen_index) => { + let target_output = &self.outputs[target_screen_index - 1]; + + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(&target_output.name); + self.send_command(&focus_cmd); + } + None => {} + }; + // Then we focus the workspace + let mut focus_cmd: String = "workspace number ".to_string(); + focus_cmd.push_str(&workspace_index.to_string()); + self.send_command(&focus_cmd); + } + } + } + + fn focus_to_workspace_relative(&self, workspace_index: usize) { + let current_workspace_index: usize = self.get_current_workspace_index(); + let focused_output_index = current_workspace_index / MAX_GROUP_WS; + + let mut cmd: String = "workspace number ".to_string(); + let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index); + cmd.push_str(&full_ws_name); + self.send_command(&cmd); + } + + fn focus_to_group(&self, group_index: usize) { + let current_workspace_index: usize = self.get_current_workspace_index(); + let target_workspace_relative_index = current_workspace_index % MAX_GROUP_WS; + + let target_workspace_index = group_index * MAX_GROUP_WS + target_workspace_relative_index; + let full_ws_name = format!( + "{}", + group_index * MAX_GROUP_WS + target_workspace_relative_index + ); + + // If the workspace already exists + match self + .workspaces + .iter() + .find(|w| w.num == target_workspace_index) + { + Some(_) => { + let mut focus_cmd: String = "workspace number ".to_string(); + focus_cmd.push_str(&full_ws_name); + self.send_command(&focus_cmd); + } + None => { + let target_screen_index = match self + .workspaces + .iter() + .find(|w| w.num / MAX_GROUP_WS == group_index) + { + // If other workspaces on the same group exists + Some(other_workspace) => Some( + self.outputs + .iter() + .enumerate() + .find(|i| i.1.name == other_workspace.output) + .unwrap() + .0 + + 1, + ), + None => { + // Or if the targeted output is currently connected + if group_index > 0 && group_index <= self.outputs.len() { + Some(group_index) + } else { + None + } + } + }; + + match target_screen_index { + // If we have to send it to another screen + Some(target_screen_index) => { + let target_output = &self.outputs[target_screen_index - 1]; + + let mut focus_cmd: String = "focus output ".to_string(); + focus_cmd.push_str(&target_output.name); + self.send_command(&focus_cmd); + } + None => {} + }; + // Then we focus the workspace + let mut focus_cmd: String = "workspace number ".to_string(); + focus_cmd.push_str(&target_workspace_index.to_string()); + self.send_command(&focus_cmd); + } + } + } + + fn focus_all_outputs_to_workspace(&self, workspace_index: usize) { + let current_output = self.get_current_output_name(); + + // Iterate on all outputs to focus on the given workspace + for output in self.outputs.iter() { + let mut cmd: String = "focus output ".to_string(); + cmd.push_str(output.name.as_str()); + self.send_command(&cmd); + + self.focus_to_workspace(workspace_index); + } + + // Get back to currently focused output + let mut cmd: String = "focus output ".to_string(); + cmd.push_str(¤t_output); + self.send_command(&cmd); + } + + fn move_container_to_next_output(&self) { + self.move_container_to_next_or_prev_output(false); + } + + fn move_container_to_prev_output(&self) { + self.move_container_to_next_or_prev_output(true); + } + + fn move_container_to_next_or_prev_output(&self, go_to_prev: bool) { + let focused_output_index = self.get_current_output_index(); + + let target_output = if go_to_prev { + &self.outputs[(focused_output_index + self.outputs.len() - 1) % self.outputs.len()] + } else { + &self.outputs[(focused_output_index + 1) % self.outputs.len()] + }; + + let workspaces = self.get_workspaces(); + let target_workspace = workspaces + .iter() + .find(|x| x.output == target_output.name && x.visible) + .unwrap(); + let group_index = (target_workspace.num / MAX_GROUP_WS) as usize; + let full_ws_name = format!( + "{}", + group_index * MAX_GROUP_WS + target_workspace.num % MAX_GROUP_WS + ); + + // Move container to target workspace + let mut cmd: String = "move container to workspace number ".to_string(); + cmd.push_str(&full_ws_name); + self.send_command(&cmd); + + // Focus that workspace to follow the container + let mut cmd: String = "workspace number ".to_string(); + cmd.push_str(&full_ws_name); + self.send_command(&cmd); + } + + fn focus_container_to_next_group(&self) { + self.focus_container_to_next_or_prev_group(false); + } + + fn focus_container_to_prev_group(&self) { + self.focus_container_to_next_or_prev_group(true); + } + + fn focus_container_to_next_or_prev_group(&self, go_to_prev: bool) { + let current_workspace_index: usize = self.get_current_workspace_index(); + let focused_group_index = current_workspace_index / MAX_GROUP_WS; + + if go_to_prev { + self.focus_to_group(focused_group_index - 1); + } else { + self.focus_to_group(focused_group_index + 1); + }; + } + + fn init_workspaces(&self, workspace_index: usize) { + let cmd_prefix: String = "focus output ".to_string(); + for output in self.outputs.iter().rev() { + let mut cmd = cmd_prefix.clone(); + cmd.push_str(output.name.as_str()); + self.send_command(&cmd); + + let mut cmd: String = "workspace number ".to_string(); + let full_ws_name = format!( + "{}", + (self.get_current_output_index() + 1) * MAX_GROUP_WS + workspace_index + ); + cmd.push_str(&full_ws_name); + self.send_command(&cmd); + } + } + + fn rearrange_workspaces(&self) { + let focus_cmd_prefix: String = "workspace number ".to_string(); + let move_cmd_prefix: String = "move workspace to ".to_string(); + for workspace in self.workspaces.iter() { + let mut focus_cmd = focus_cmd_prefix.clone(); + focus_cmd.push_str(&workspace.num.to_string()); + self.send_command(&focus_cmd); + + let group_index = workspace.num / MAX_GROUP_WS; + if group_index <= self.outputs.len() - 1 { + let mut move_cmd = move_cmd_prefix.clone(); + move_cmd.push_str(&self.outputs[group_index.max(1) - 1].name); + self.send_command(&move_cmd); + } } } } fn main() { let cli = Cli::parse(); - let stream = get_stream(); + + let swaysome = SwaySome::new(); match &cli.command { Command::Init(action) => { - init_workspaces(&stream, action.index); + swaysome.init_workspaces(action.index); } Command::Move(action) => { - move_container_to_workspace(&stream, action.index); + swaysome.move_container_to_workspace(action.index); } Command::MoveToGroup(action) => { - move_container_to_workspace_group(&stream, action.index); + swaysome.move_container_to_workspace_group(action.index); } Command::Focus(action) => { - focus_to_workspace(&stream, action.index); + swaysome.focus_to_workspace(action.index); } Command::FocusGroup(action) => { - focus_to_group(&stream, action.index); + swaysome.focus_to_group(action.index); } Command::FocusAllOutputs(action) => { - focus_all_outputs_to_workspace(&stream, action.index); + swaysome.focus_all_outputs_to_workspace(action.index); } Command::NextOutput => { - move_container_to_next_output(&stream); + swaysome.move_container_to_next_output(); } Command::PrevOutput => { - move_container_to_prev_output(&stream); + swaysome.move_container_to_prev_output(); } Command::NextGroup => { - focus_container_to_next_group(&stream); + swaysome.focus_container_to_next_group(); } Command::PrevGroup => { - focus_container_to_prev_group(&stream); + swaysome.focus_container_to_prev_group(); } Command::RearrangeWorkspaces => { - rearrange_workspaces(&stream); + swaysome.rearrange_workspaces(); } } }