use clap::{Parser, Subcommand, ValueEnum}; mod hyprland_ipc; use hyprland::{ data::{Client, Monitor, Transforms, Workspaces}, dispatch::Direction, shared::HyprData, }; use hyprland_ipc::{client, monitor, option, workspace}; #[derive(Parser)] #[command(name = "hyprsome")] #[command(author = "sopa0")] #[command(version = "0.1.11")] #[command(about = "Makes hyprland workspaces behave like awesome")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { Focus { direction: Directions }, Workspace { workspace_number: u64 }, Move { workspace_number: u64 }, Movefocus { workspace_number: u64 }, MoveEmpty, FocusEmpty, } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] enum Directions { L, R, U, D, } pub trait MonitorDimensions { fn real_width(&self) -> f32; fn real_height(&self) -> f32; } impl MonitorDimensions for Monitor { fn real_width(&self) -> f32 { match self.transform { Transforms::Normal | Transforms::Normal180 | Transforms::Flipped | Transforms::Flipped180 => self.width as f32 / self.scale, Transforms::Normal90 | Transforms::Normal270 | Transforms::Flipped90 => { self.height as f32 / self.scale } _ => self.width as f32, } } fn real_height(&self) -> f32 { match self.transform { Transforms::Normal | Transforms::Flipped | Transforms::Normal180 | Transforms::Flipped180 => self.height as f32 / self.scale, Transforms::Normal90 | Transforms::Normal270 | Transforms::Flipped90 => { self.width as f32 / self.scale } _ => self.height as f32, } } } pub fn get_current_monitor() -> Monitor { monitor::get().find(|m| m.focused).unwrap() } //TODO: refactor this nonsense pub fn select_workspace(workspace_number: &u64) { let mon = get_current_monitor(); match mon.id { 0 => workspace::focus(workspace_number), _ => { workspace::focus( &format!("{}{}", mon.id, workspace_number) .parse::() .unwrap(), ); } } } pub fn get_empty_workspace() -> u64 { let mon = get_current_monitor(); let mut found = Vec::new(); for workspaces in Workspaces::get().iter() { for workspace in workspaces.iter() { if workspace.monitor == mon.name { let mut id = workspace.name.clone(); if id.len() > 1 { id = id.chars().nth(1).unwrap().to_string(); } found.push(id); } } } for i in 1..9 { if !found.contains(&i.to_string()) { return i; } } return 1; // Send to the first workspace if no others are available } //TODO: refactor this nonsense pub fn send_to_workspace(workspace_number: &u64) { let mon = get_current_monitor(); match mon.id { 0 => workspace::move_to(workspace_number), _ => { workspace::move_to( &format!("{}{}", mon.id, workspace_number) .parse::() .unwrap(), ); } } } //TODO: refactor this nonsense pub fn movefocus(workspace_number: &u64) { let mon = get_current_monitor(); match mon.id { 0 => workspace::move_focus(workspace_number), _ => { workspace::move_focus( &format!("{}{}", mon.id, workspace_number) .parse::() .unwrap(), ); } } } pub fn get_leftmost_client_for_monitor(mon_id: i16) -> Client { let clients = client::get(); clients .into_iter() .filter(|c| c.monitor == mon_id) .min_by_key(|c| c.at.0) .unwrap() } pub fn focus_left(aw: Client) { let mon = monitor::get_by_id(aw.monitor); let is_leftmost_client = is_leftmost_client(&aw, &mon); if is_leftmost_monitor(&mon) && is_leftmost_client { return; } if is_leftmost_client { monitor::focus_left(); return; } client::focus_by_direction(Direction::Left); } pub fn focus_right(aw: Client) { let mon = monitor::get_by_id(aw.monitor); if is_rightmost_monitor(&mon) && is_rightmost_client(&aw, &mon) { return; } if is_rightmost_client(&aw, &mon) { monitor::focus_right(); return; } client::focus_by_direction(Direction::Right); } pub fn focus_up(aw: Client) { let mon = monitor::get_by_id(aw.monitor); let is_top_client = is_top_client(&aw, &mon); if is_top_monitor(&mon) && is_top_client { return; } if is_top_client { monitor::focus_up(); return; } client::focus_by_direction(Direction::Up); } pub fn focus_down(aw: Client) { let mon = monitor::get_by_id(aw.monitor); let is_bottom_client = is_bottom_client(&aw, &mon); if is_bottom_monitor(&mon) && is_bottom_client { return; } if is_bottom_client { monitor::focus_down(); return; } client::focus_by_direction(Direction::Down); } pub fn is_leftmost_client(aw: &Client, mon: &Monitor) -> bool { let gaps = option::get_gaps(); if (aw.at.0 - gaps) as i32 == mon.x { return true; } false } pub fn is_rightmost_client(aw: &Client, mon: &Monitor) -> bool { let gaps = option::get_gaps(); if mon.real_width() + mon.x as f32 - gaps as f32 == aw.size.0 as f32 + aw.at.0 as f32 { return true; } false } pub fn is_top_client(aw: &Client, mon: &Monitor) -> bool { let gaps = option::get_gaps(); if mon.y + (gaps as i32) + (mon.reserved.1 as i32) == (aw.at.1 as i32) { return true; } false } pub fn is_bottom_client(aw: &Client, mon: &Monitor) -> bool { let gaps = option::get_gaps(); if mon.real_height() + mon.y as f32 - gaps as f32 - mon.reserved.1 as f32 == aw.size.1 as f32 + gaps as f32 { return true; } false } pub fn is_rightmost_monitor(mon: &Monitor) -> bool { let monitors = monitor::get(); let max = monitors.into_iter().max_by_key(|m| m.x).unwrap(); if max.x == mon.x { return true; } false } pub fn is_leftmost_monitor(mon: &Monitor) -> bool { let monitors = monitor::get(); let min = monitors.into_iter().min_by_key(|m| m.x).unwrap(); if min.x == mon.x { return true; } false } pub fn is_top_monitor(mon: &Monitor) -> bool { let monitors = monitor::get(); let min = monitors.into_iter().min_by_key(|m| m.y).unwrap(); if min.y == mon.y { return true; } false } pub fn is_bottom_monitor(mon: &Monitor) -> bool { let monitors = monitor::get(); let max = monitors.into_iter().max_by_key(|m| m.y).unwrap(); if max.y == mon.y { return true; } false } fn main() { let cli = Cli::parse(); match &cli.command { Commands::Focus { direction } => match direction { Directions::L => { let aw = client::get_active(); match aw { Some(aw) => focus_left(aw), None => monitor::focus_left(), }; } Directions::R => { let aw = client::get_active(); match aw { Some(aw) => focus_right(aw), None => monitor::focus_right(), }; } Directions::U => { let aw = client::get_active(); match aw { Some(aw) => focus_up(aw), None => monitor::focus_up(), }; } Directions::D => { let aw = client::get_active(); match aw { Some(aw) => focus_down(aw), None => monitor::focus_down(), }; } }, Commands::Workspace { workspace_number } => { select_workspace(workspace_number); } Commands::Move { workspace_number } => { send_to_workspace(workspace_number); } Commands::Movefocus { workspace_number } => { movefocus(workspace_number); } Commands::MoveEmpty => { movefocus(&get_empty_workspace()); }, Commands::FocusEmpty => { select_workspace(&get_empty_workspace()); }, } }