Split the whole thing into a lib and tiny main

This is how binaries are supposed to be done in Rust, to help with
integration testing. Guess what's coming?
This commit is contained in:
Skia 2024-10-27 09:42:49 +01:00
parent 191a5ab001
commit e5c61a8694
2 changed files with 645 additions and 643 deletions

644
src/lib.rs Normal file
View file

@ -0,0 +1,644 @@
extern crate byteorder;
extern crate serde_json;
use serde::{Deserialize, Serialize};
use std::cell::Cell;
use std::env;
use std::io::Cursor;
use std::io::{Read, Write};
use std::mem;
use std::os::unix::net::UnixStream;
use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
// Maximum workspaces per group. This will determine the naming.
// Examples:
// 10 → 17, 27
// 100 → 107, 207
// 15 → 22, 37
const MAX_GROUP_WS: usize = 10;
const RUN_COMMAND: u32 = 0;
const GET_WORKSPACES: u32 = 1;
// const SUBSCRIBE: u32 = 2;
const GET_OUTPUTS: u32 = 3;
pub struct SwaySome {
socket: Cell<Option<UnixStream>>,
outputs: Vec<Output>,
// current_output: Output,
workspaces: Vec<Workspace>,
// current_workspace: Workspace,
}
#[derive(Serialize, Deserialize, Debug)]
struct Output {
name: String,
#[serde(default)]
focused: bool,
#[serde(default)]
active: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct Workspace {
num: usize,
output: String,
visible: bool,
}
impl SwaySome {
pub 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_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(&self, msg_type: u32, payload: &str) {
let payload_length = payload.len() as u32;
let mut msg_prefix: [u8; 6 * mem::size_of::<u8>() + 2 * mem::size_of::<u32>()] =
*b"i3-ipc00000000";
msg_prefix[6..]
.as_mut()
.write_u32::<LittleEndian>(payload_length)
.expect("Unable to write");
msg_prefix[10..]
.as_mut()
.write_u32::<LittleEndian>(msg_type)
.expect("Unable to write");
let mut msg: Vec<u8> = 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<String, &str> {
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::<LittleEndian>().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::Value> = 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<Output> {
self.send_msg(GET_OUTPUTS, "");
let o = match self.read_msg() {
Ok(msg) => msg,
Err(_) => panic!("Unable to get outputs"),
};
let mut outputs: Vec<Output> = serde_json::from_str::<Vec<Output>>(&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<Workspace> {
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<Workspace> = serde_json::from_str(&ws).unwrap();
workspaces.sort_by_key(|x| x.num);
workspaces
}
fn get_current_output_index(&self) -> usize {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
match outputs.iter().position(|x| x.focused) {
Some(i) => i,
None => panic!("WTF! No focused output???"),
}
}
fn get_current_output_name(&self) -> String {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
let focused_output_index = match 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 {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
// Do not use `self.workspaces`, as the information here could be outdated, especially the `visible` attribute
self.get_workspaces()
.iter()
.find(|w| w.visible && outputs.iter().find(|o| o.name == w.output).unwrap().focused)
.unwrap()
.num
}
pub 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);
}
}
pub 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_workspace_absolute(&self, workspace_index: usize) {
let group_index = (workspace_index / MAX_GROUP_WS) as usize;
let full_ws_name = format!(
"{}",
group_index * MAX_GROUP_WS + workspace_index % MAX_GROUP_WS
);
// 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(&current_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(&current_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 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 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);
}
pub 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);
}
pub 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);
}
}
}
pub 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(&current_output);
self.send_command(&cmd);
}
pub fn move_container_to_next_output(&self) {
self.move_container_to_next_or_prev_output(false);
}
pub 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);
}
pub fn move_workspace_group_to_next_output(&self) {
self.move_workspace_group_to_next_or_prev_output(false);
}
pub fn move_workspace_group_to_prev_output(&self) {
self.move_workspace_group_to_next_or_prev_output(true);
}
fn move_workspace_group_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 current_workspace = self.get_current_workspace_index();
let current_group_index = (current_workspace / MAX_GROUP_WS) as usize;
for workspace in self.get_workspaces() {
let ws_index = workspace.num / MAX_GROUP_WS;
if ws_index == current_group_index {
let cmd: String = format!("workspace number {}", workspace.num);
self.send_command(&cmd);
let cmd: String = format!("move workspace to {}", target_output.name);
self.send_command(&cmd);
}
}
let cmd: String = format!("workspace number {}", current_workspace);
self.send_command(&cmd);
}
pub fn focus_container_to_next_group(&self) {
self.focus_container_to_next_or_prev_group(false);
}
pub 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;
let highest_group = self.workspaces.last().unwrap().num / MAX_GROUP_WS;
let target_group;
if go_to_prev {
if focused_group_index == 0 {
target_group = highest_group;
} else {
target_group = focused_group_index - 1;
}
} else {
if focused_group_index >= highest_group {
target_group = 0;
} else {
target_group = focused_group_index + 1;
}
};
self.focus_to_group(target_group);
}
pub 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);
}
}
pub 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);
}
}
}
}

View file

@ -1,31 +1,7 @@
extern crate byteorder;
extern crate clap; extern crate clap;
extern crate serde_json;
use serde::{Deserialize, Serialize};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use std::cell::Cell; use swaysome::SwaySome;
use std::env;
use std::io::Cursor;
use std::io::{Read, Write};
use std::mem;
use std::os::unix::net::UnixStream;
use std::path::Path;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
// Maximum workspaces per group. This will determine the naming.
// Examples:
// 10 → 17, 27
// 100 → 107, 207
// 15 → 22, 37
const MAX_GROUP_WS: usize = 10;
const RUN_COMMAND: u32 = 0;
const GET_WORKSPACES: u32 = 1;
// const SUBSCRIBE: u32 = 2;
const GET_OUTPUTS: u32 = 3;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(author, version, about = "Better multimonitor handling for sway", long_about = None)] #[clap(author, version, about = "Better multimonitor handling for sway", long_about = None)]
@ -87,624 +63,6 @@ struct Index {
index: usize, index: usize,
} }
struct SwaySome {
socket: Cell<Option<UnixStream>>,
outputs: Vec<Output>,
// current_output: Output,
workspaces: Vec<Workspace>,
// current_workspace: Workspace,
}
#[derive(Serialize, Deserialize, Debug)]
struct Output {
name: String,
#[serde(default)]
focused: bool,
#[serde(default)]
active: bool,
}
#[derive(Serialize, Deserialize, Debug)]
struct Workspace {
num: usize,
output: String,
visible: bool,
}
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_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(&self, msg_type: u32, payload: &str) {
let payload_length = payload.len() as u32;
let mut msg_prefix: [u8; 6 * mem::size_of::<u8>() + 2 * mem::size_of::<u32>()] =
*b"i3-ipc00000000";
msg_prefix[6..]
.as_mut()
.write_u32::<LittleEndian>(payload_length)
.expect("Unable to write");
msg_prefix[10..]
.as_mut()
.write_u32::<LittleEndian>(msg_type)
.expect("Unable to write");
let mut msg: Vec<u8> = 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<String, &str> {
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::<LittleEndian>().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::Value> = 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<Output> {
self.send_msg(GET_OUTPUTS, "");
let o = match self.read_msg() {
Ok(msg) => msg,
Err(_) => panic!("Unable to get outputs"),
};
let mut outputs: Vec<Output> = serde_json::from_str::<Vec<Output>>(&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<Workspace> {
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<Workspace> = serde_json::from_str(&ws).unwrap();
workspaces.sort_by_key(|x| x.num);
workspaces
}
fn get_current_output_index(&self) -> usize {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
match outputs.iter().position(|x| x.focused) {
Some(i) => i,
None => panic!("WTF! No focused output???"),
}
}
fn get_current_output_name(&self) -> String {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
let focused_output_index = match 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 {
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
let outputs = self.get_outputs();
// Do not use `self.workspaces`, as the information here could be outdated, especially the `visible` attribute
self.get_workspaces()
.iter()
.find(|w| w.visible && outputs.iter().find(|o| o.name == w.output).unwrap().focused)
.unwrap()
.num
}
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_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_workspace_absolute(&self, workspace_index: usize) {
let group_index = (workspace_index / MAX_GROUP_WS) as usize;
let full_ws_name = format!(
"{}",
group_index * MAX_GROUP_WS + workspace_index % MAX_GROUP_WS
);
// 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(&current_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(&current_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 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 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);
}
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(&current_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 move_workspace_group_to_next_output(&self) {
self.move_workspace_group_to_next_or_prev_output(false);
}
fn move_workspace_group_to_prev_output(&self) {
self.move_workspace_group_to_next_or_prev_output(true);
}
fn move_workspace_group_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 current_workspace = self.get_current_workspace_index();
let current_group_index = (current_workspace / MAX_GROUP_WS) as usize;
for workspace in self.get_workspaces() {
let ws_index = workspace.num / MAX_GROUP_WS;
if ws_index == current_group_index {
let cmd: String = format!("workspace number {}", workspace.num);
self.send_command(&cmd);
let cmd: String = format!("move workspace to {}", target_output.name);
self.send_command(&cmd);
}
}
let cmd: String = format!("workspace number {}", current_workspace);
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;
let highest_group = self.workspaces.last().unwrap().num / MAX_GROUP_WS;
let target_group;
if go_to_prev {
if focused_group_index == 0 {
target_group = highest_group;
} else {
target_group = focused_group_index - 1;
}
} else {
if focused_group_index >= highest_group {
target_group = 0;
} else {
target_group = focused_group_index + 1;
}
};
self.focus_to_group(target_group);
}
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() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();