diff --git a/Cargo.lock b/Cargo.lock index 67be7da..5714620 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -209,6 +215,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.2.9" @@ -317,6 +363,7 @@ dependencies = [ "clap", "directories", "lazy_static", + "rand", "regex", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 5a72198..57a76e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ toml = "0.5.8" directories = "3.0" regex = "1.5.4" lazy_static = "1.4.0" - +rand = "0.8.4" diff --git a/src/cmds.rs b/src/cmds.rs index 790a9c8..16a43c0 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -16,38 +16,73 @@ //! Functions and data structures of the swayr client. use crate::con; +use crate::con::NodeMethods; use crate::config as cfg; +use crate::layout; use crate::util; use crate::util::DisplayFormat; +use clap::Clap; +use rand; +use rand::prelude::SliceRandom; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use std::sync::RwLock; use swayipc as s; -use clap::Clap; +#[derive(Clap, Debug, Deserialize, Serialize, PartialEq)] +pub enum ConsiderFloating { + /// Include floating windows. + IncludeFloating, + /// Exclude floating windows. + ExcludeFloating, +} #[derive(Clap, Debug, Deserialize, Serialize)] pub enum SwayrCommand { /// Switch to next urgent window (if any) or to last recently used window. SwitchToUrgentOrLRUWindow, - /// Focus the selected window + /// Focus the selected window. SwitchWindow, /// Focus the next window. NextWindow, /// Focus the previous window. PrevWindow, - /// Quit the selected window + /// Quit the selected window. QuitWindow, - /// Switch to the selected workspace + /// Switch to the selected workspace. SwitchWorkspace, - /// Switch to the selected workspace or focus the selected window + /// Switch to the selected workspace or focus the selected window. SwitchWorkspaceOrWindow, - /// Quit all windows of selected workspace or the selected window + /// Quit all windows of selected workspace or the selected window. QuitWorkspaceOrWindow, - /// Select and execute a swaymsg command + /// Tab or shuffle-and-tile the windows on the current workspace, including + /// or excluding floating windows. + ToggleTabShuffleTileWorkspace { + #[clap(subcommand)] + floating: ConsiderFloating, + }, + /// Tiles the windows on the current workspace, including or excluding + /// floating windows. + TileWorkspace { + #[clap(subcommand)] + floating: ConsiderFloating, + }, + /// Tabs the windows on the current workspace, including or excluding + /// floating windows. + TabWorkspace { + #[clap(subcommand)] + floating: ConsiderFloating, + }, + /// Shuffles and tiles the windows on the current workspace, including or + /// excluding floating windows. + ShuffleTileWorkspace { + #[clap(subcommand)] + floating: ConsiderFloating, + }, + /// Select and execute a swaymsg command. ExecuteSwaymsgCommand, - /// Select and execute a swayr command + /// Select and execute a swayr command. ExecuteSwayrCommand, } @@ -90,6 +125,24 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { SwayrCommand::QuitWorkspaceOrWindow => { quit_workspace_or_window(Some(&*props.read().unwrap())) } + SwayrCommand::TileWorkspace { floating } => tile_current_workspace( + floating == &ConsiderFloating::IncludeFloating, + false, + ), + SwayrCommand::TabWorkspace { floating } => tab_current_workspace( + floating == &ConsiderFloating::IncludeFloating, + ), + SwayrCommand::ShuffleTileWorkspace { floating } => { + tile_current_workspace( + floating == &ConsiderFloating::IncludeFloating, + true, + ) + } + SwayrCommand::ToggleTabShuffleTileWorkspace { floating } => { + toggle_tab_tile_current_workspace( + floating == &ConsiderFloating::IncludeFloating, + ) + } SwayrCommand::ExecuteSwaymsgCommand => exec_swaymsg_command(), SwayrCommand::ExecuteSwayrCommand => { if let Some(c) = util::select_from_menu( @@ -102,6 +155,30 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { SwayrCommand::SwitchWorkspace, SwayrCommand::SwitchWorkspaceOrWindow, SwayrCommand::SwitchToUrgentOrLRUWindow, + SwayrCommand::ToggleTabShuffleTileWorkspace { + floating: ConsiderFloating::ExcludeFloating, + }, + SwayrCommand::ToggleTabShuffleTileWorkspace { + floating: ConsiderFloating::IncludeFloating, + }, + SwayrCommand::TileWorkspace { + floating: ConsiderFloating::ExcludeFloating, + }, + SwayrCommand::TileWorkspace { + floating: ConsiderFloating::IncludeFloating, + }, + SwayrCommand::TabWorkspace { + floating: ConsiderFloating::ExcludeFloating, + }, + SwayrCommand::TabWorkspace { + floating: ConsiderFloating::IncludeFloating, + }, + SwayrCommand::ShuffleTileWorkspace { + floating: ConsiderFloating::ExcludeFloating, + }, + SwayrCommand::ShuffleTileWorkspace { + floating: ConsiderFloating::IncludeFloating, + }, SwayrCommand::NextWindow, SwayrCommand::PrevWindow, ], @@ -258,6 +335,94 @@ pub fn quit_workspace_or_window( } } +fn tile_current_workspace(include_floating: bool, shuffle: bool) { + match layout::relayout_current_workspace( + include_floating, + Box::new(move |wins, con: &mut s::Connection| { + con.run_command("focus parent".to_string())?; + con.run_command("layout splith".to_string())?; + + let mut placed_wins = vec![]; + let mut rng = rand::thread_rng(); + if shuffle { + wins.shuffle(&mut rng); + } else { + wins.reverse() + } + for win in wins { + std::thread::sleep(std::time::Duration::from_millis(25)); + con.run_command(format!( + "[con_id={}] move to workspace current", + win.get_id() + ))?; + std::thread::sleep(std::time::Duration::from_millis(25)); + con.run_command(format!( + "[con_id={}] floating disable", + win.get_id() + ))?; + placed_wins.push(win); + if shuffle { + std::thread::sleep(std::time::Duration::from_millis(25)); + if let Some(win) = placed_wins.choose(&mut rng) { + con.run_command(format!( + "[con_id={}] focus", + win.get_id() + ))?; + } + } + } + Ok(()) + }), + ) { + Ok(_) => (), + Err(err) => eprintln!("Error retiling workspace: {:?}", err), + } +} + +fn tab_current_workspace(include_floating: bool) { + match layout::relayout_current_workspace( + include_floating, + Box::new(move |wins, con: &mut s::Connection| { + con.run_command("focus parent".to_string())?; + con.run_command("layout tabbed".to_string())?; + + let mut placed_wins = vec![]; + wins.reverse(); + for win in wins { + std::thread::sleep(std::time::Duration::from_millis(25)); + con.run_command(format!( + "[con_id={}] move to workspace current", + win.get_id() + ))?; + std::thread::sleep(std::time::Duration::from_millis(25)); + con.run_command(format!( + "[con_id={}] floating disable", + win.get_id() + ))?; + placed_wins.push(win); + } + Ok(()) + }), + ) { + Ok(_) => (), + Err(err) => eprintln!("Error retiling workspace: {:?}", err), + } +} + +fn toggle_tab_tile_current_workspace(include_floating: bool) { + let tree = get_tree(); + let workspaces = tree.workspaces(); + let cur_ws = workspaces + .iter() + .find(|w| con::is_current_container(w)) + .unwrap(); + if cur_ws.layout == s::NodeLayout::Tabbed { + tile_current_workspace(include_floating, true); + } else { + tab_current_workspace(include_floating); + } +} + fn get_swaymsg_commands<'a>() -> Vec> { let mut cmds = vec![]; diff --git a/src/con.rs b/src/con.rs index bb18837..0f4561a 100644 --- a/src/con.rs +++ b/src/con.rs @@ -153,6 +153,10 @@ impl Window<'_> { pub fn is_focused(&self) -> bool { self.node.focused } + + pub fn is_floating(&self) -> bool { + self.node.node_type == s::NodeType::FloatingCon + } } impl PartialEq for Window<'_> { @@ -433,6 +437,14 @@ impl Workspace<'_> { pub fn is_scratchpad(&self) -> bool { self.node.is_scratchpad() } + + pub fn is_current(&self) -> bool { + is_current_container(self.node) + } +} + +pub fn is_current_container(node: &s::Node) -> bool { + node.focused || NodeIter::new(node).any(|c| c.focused) } impl PartialEq for Workspace<'_> { diff --git a/src/layout.rs b/src/layout.rs index 4cc51f1..eb21188 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -15,6 +15,7 @@ //! Functions and data structures of the swayrd demon. +use crate::cmds; use crate::con; use crate::con::NodeMethods; use crate::config; @@ -123,3 +124,48 @@ pub fn maybe_auto_tile(config: &config::Config) { println!("auto_tile: end\n"); } } + +pub fn relayout_current_workspace( + include_floating: bool, + insert_win_fn: Box< + dyn Fn(&mut [&con::Window], &mut s::Connection) -> s::Fallible<()>, + >, +) -> s::Fallible<()> { + let root = cmds::get_tree(); + let workspaces = con::get_workspaces(&root, false, None); + if let Some(cur_ws) = workspaces.iter().find(|ws| ws.is_current()) { + if let Ok(mut con) = s::Connection::new() { + let mut moved_wins: Vec<&con::Window> = vec![]; + let mut focused_win = None; + for win in &cur_ws.windows { + if win.is_focused() { + focused_win.insert(win); + } + if !include_floating && win.is_floating() { + continue; + } + moved_wins.push(win); + con.run_command(format!( + "[con_id={}] move to scratchpad", + win.get_id() + ))?; + } + + insert_win_fn(moved_wins.as_mut_slice(), &mut con)?; + std::thread::sleep(std::time::Duration::from_millis(25)); + + if let Some(win) = focused_win { + con.run_command(format!("[con_id={}] focus", win.get_id()))?; + } + Ok(()) + } else { + Err(s::Error::CommandFailed( + "Cannot create connection.".to_string(), + )) + } + } else { + Err(s::Error::CommandFailed( + "No workspace is focused.".to_string(), + )) + } +}