From cdba9b1186a937e7a52ceb9eedf35c9b27b26d3c Mon Sep 17 00:00:00 2001 From: Tassilo Horn Date: Fri, 12 Feb 2021 23:08:10 +0100 Subject: [PATCH] New commands: next-window & prev-window --- README.md | 31 +++++++++++++++++++++----- src/client.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++----- src/con.rs | 25 +++++++++++++-------- 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7802908..e503a1b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,12 @@ window/workspace creations, deletions, and focus changes using sway's JSON IPC interface. The client `swayr` offers subcommands, see `swayr --help`. Right now, there are these subcommands: -* `switch-window` displays all windows in the order urgent first, then LRU, - focused last and focuses the selected. +* `next-window` focuses the next window in depth-first iteration order of the + tree. +* `prev-window` focuses the previous window in depth-first iteration order of + the tree. +* `switch-window` displays all windows in the order urgent first, then + last-recently-used, focused last and focuses the selected. * `quit-window` displays all windows and quits the selected one. * `switch-to-urgent-or-lru-window` switches to the next window with urgency hint (if any) or to the last recently used window. @@ -43,14 +47,29 @@ Next to starting the demon, you want to bind swayr commands to some keys like so: ``` -bindsym $mod+Delete exec env RUST_BACKTRACE=1 swayr quit-window > /tmp/swayr.log 2>&1 -bindsym $mod+Space exec env RUST_BACKTRACE=1 swayr switch-window >> /tmp/swayr.log 2>&1 +bindsym $mod+Space exec env RUST_BACKTRACE=1 \ + swayr switch-window >> /tmp/swayr.log 2>&1 + +bindsym $mod+Delete exec env RUST_BACKTRACE=1 \ + swayr quit-window > /tmp/swayr.log 2>&1 + bindsym $mod+Tab exec env RUST_BACKTRACE=1 \ swayr switch-to-urgent-or-lru-window >> /tmp/swayr.log 2>&1 + +bindsym $mod+Next exec env RUST_BACKTRACE=1 \ + swayr next-window >> /tmp/swayr.log 2>&2 + +bindsym $mod+Prior exec env RUST_BACKTRACE=1 \ + swayr prev-window >> /tmp/swayr.log 2>&2 + bindsym $mod+Shift+Space exec env RUST_BACKTRACE=1 \ swayr switch-workspace-or-window >> /tmp/swayr.log 2>&1 -bindsym $mod+c exec env RUST_BACKTRACE=1 swayr execute-swaymsg-command >> /tmp/swayr.log 2>&1 -bindsym $mod+Shift+c exec env RUST_BACKTRACE=1 swayr execute-swayr-command >> /tmp/swa + +bindsym $mod+c exec env RUST_BACKTRACE=1 \ + swayr execute-swaymsg-command >> /tmp/swayr.log 2>&1 + +bindsym $mod+Shift+c exec env RUST_BACKTRACE=1 \ + swayr execute-swayr-command >> /tmp/swa ``` Of course, configure the keys to your liking. Again, enabling rust backtraces diff --git a/src/client.rs b/src/client.rs index 662617a..8704062 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,6 +12,10 @@ pub enum SwayrCommand { SwitchToUrgentOrLRUWindow, /// Focus the selected window SwitchWindow, + /// Focus the next window. + NextWindow, + /// Focus the previous window. + PrevWindow, /// Quit the selected window QuitWindow, /// Switch to the selected workspace @@ -38,6 +42,12 @@ pub fn exec_swayr_cmd(cmd: &SwayrCommand) { switch_to_urgent_or_lru_window() } SwayrCommand::SwitchWindow => switch_window(), + SwayrCommand::NextWindow => { + focus_next_window_in_direction(Direction::Forward) + } + SwayrCommand::PrevWindow => { + focus_next_window_in_direction(Direction::Backward) + } SwayrCommand::QuitWindow => quit_window(), SwayrCommand::SwitchWorkspace => switch_workspace(), SwayrCommand::SwitchWorkspaceOrWindow => switch_workspace_or_window(), @@ -53,6 +63,9 @@ pub fn exec_swayr_cmd(cmd: &SwayrCommand) { SwayrCommand::SwitchWindow, SwayrCommand::SwitchWorkspace, SwayrCommand::SwitchWorkspaceOrWindow, + SwayrCommand::SwitchToUrgentOrLRUWindow, + SwayrCommand::NextWindow, + SwayrCommand::PrevWindow, ], ) { exec_swayr_cmd(c); @@ -71,12 +84,11 @@ fn quit_window_by_id(id: ipc::Id) { pub fn switch_to_urgent_or_lru_window() { let root = con::get_tree(); - let windows = con::get_windows(&root); + let windows = con::get_windows(&root, true); if let Some(win) = windows .iter() - .filter(|w| w.is_urgent()) - .next() - .or_else(|| windows.iter().next()) + .find(|w| w.is_urgent()) + .or_else(|| windows.get(0)) { println!("Switching to {}", win); focus_window_by_id(win.get_id()) @@ -87,13 +99,50 @@ pub fn switch_to_urgent_or_lru_window() { pub fn switch_window() { let root = con::get_tree(); - let windows = con::get_windows(&root); + let windows = con::get_windows(&root, true); if let Some(window) = con::select_window("Switch to window", &windows) { focus_window_by_id(window.get_id()) } } +pub enum Direction { + Backward, + Forward, +} + +pub fn focus_next_window_in_direction(dir: Direction) { + let root = con::get_tree(); + let windows = con::get_windows(&root, false); + + if windows.len() < 2 { + return; + } + + let pred: Box bool> = + if windows.iter().find(|w| w.is_focused()).is_none() { + let last_focused_win_id = + con::get_windows(&root, true).get(0).unwrap().get_id(); + Box::new(move |w| w.get_id() == last_focused_win_id) + } else { + Box::new(|w: &con::Window| w.is_focused()) + }; + + let mut iter: Box> = match dir { + Direction::Forward => Box::new(windows.iter().rev().cycle()), + Direction::Backward => Box::new(windows.iter().cycle()), + }; + + loop { + let win = iter.next().unwrap(); + if pred(win) { + let win = iter.next().unwrap(); + focus_window_by_id(win.get_id()); + return; + } + } +} + pub fn switch_workspace() { let root = con::get_tree(); let workspaces = con::get_workspaces(&root, false); @@ -124,7 +173,7 @@ pub fn switch_workspace_or_window() { pub fn quit_window() { let root = con::get_tree(); - let windows = con::get_windows(&root); + let windows = con::get_windows(&root, true); if let Some(window) = con::select_window("Quit window", &windows) { quit_window_by_id(window.get_id()) diff --git a/src/con.rs b/src/con.rs index 3bd44c4..d0f5b8e 100644 --- a/src/con.rs +++ b/src/con.rs @@ -144,7 +144,6 @@ fn build_windows( }) } } - v.sort(); v } @@ -184,16 +183,24 @@ fn get_con_props() -> Result, serde_json::Error> } /// Gets all application windows of the tree. -pub fn get_windows(root: &ipc::Node) -> Vec { - let con_props = match get_con_props() { - Ok(con_props) => Some(con_props), - Err(e) => { - eprintln!("Got no con_props: {:?}", e); - None +pub fn get_windows(root: &ipc::Node, sort: bool) -> Vec { + let con_props = if sort { + match get_con_props() { + Ok(con_props) => Some(con_props), + Err(e) => { + eprintln!("Got no con_props: {:?}", e); + None + } } + } else { + None }; - build_windows(root, con_props.unwrap_or_default()) + let mut wins = build_windows(root, con_props.unwrap_or_default()); + if sort { + wins.sort(); + } + wins } /// Gets all application windows of the tree. @@ -225,7 +232,7 @@ pub fn get_workspaces( #[test] fn test_get_windows() { let root = get_tree(); - let cons = get_windows(&root); + let cons = get_windows(&root, true); println!("There are {} cons.", cons.len());