// Copyright (C) 2021 Tassilo Horn // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program. If not, see . //! Functions and data structures of the swayr client. use crate::config as cfg; use crate::layout; use crate::tree as t; use crate::tree::NodeMethods; use crate::util; use crate::util::DisplayFormat; use lazy_static::lazy_static; use rand::prelude::SliceRandom; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::atomic; use std::sync::Arc; use std::sync::RwLock; use swayipc as s; pub fn run_sway_command_1(cmd: &str) { println!("Running sway command: {}", cmd); match s::Connection::new() { Ok(mut con) => { if let Err(err) = con.run_command(cmd) { eprintln!("Could not run sway command: {}", err) } } Err(err) => panic!("{}", err), } } pub fn run_sway_command(args: &[&str]) { let cmd = args.join(" "); run_sway_command_1(&cmd); } #[derive(clap::Parser, Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum ConsiderFloating { /// Include floating windows. IncludeFloating, /// Exclude floating windows. ExcludeFloating, } #[derive(clap::Parser, Debug, Deserialize, Serialize, PartialEq, Clone)] pub enum ConsiderWindows { /// Consider windows of all workspaces. AllWorkspaces, /// Consider windows of only the current workspaces. CurrentWorkspace, } #[derive(clap::Parser, Debug, Deserialize, Serialize)] pub enum SwayrCommand { /// Switch to next urgent window (if any) or to last recently used window. SwitchToUrgentOrLRUWindow, /// Focus the selected window. SwitchWindow, /// Switch to the selected workspace. SwitchWorkspace, /// Switch to the selected workspace. SwitchOutput, /// Switch to the selected workspace or focus the selected window. SwitchWorkspaceOrWindow, /// Switch to the selected workspace or focus the selected container, or /// window. SwitchWorkspaceContainerOrWindow, /// Quit the selected window. QuitWindow, /// Quit all windows of selected workspace or the selected window. QuitWorkspaceOrWindow, /// Quit all windows of selected workspace, or container or the selected /// window. QuitWorkspaceContainerOrWindow, /// Focus the next window in LRU order. NextWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the previous window in LRU order. PrevWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the next window of a tiled container. NextTiledWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the previous window of a tiled container. PrevTiledWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the next window of a tabbed or stacked container. NextTabbedOrStackedWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the previous window of a tabbed or stacked container. PrevTabbedOrStackedWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the next floating window. NextFloatingWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the previous floating window. PrevFloatingWindow { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the next window having the same layout as the current one. NextWindowOfSameLayout { #[clap(subcommand)] windows: ConsiderWindows, }, /// Focus the previous window having the same layout as the current one. PrevWindowOfSameLayout { #[clap(subcommand)] windows: ConsiderWindows, }, /// Move the currently focused window or container to the selected /// workspace. MoveFocusedToWorkspace, /// Move the currently focused window or container to the selected output, /// workspace, container or window. MoveFocusedTo, /// Swap the currently focused window or container with the selected /// container or window. SwapFocusedWith, /// 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. ExecuteSwayrCommand, /// Configure outputs. ConfigureOutputs, } impl SwayrCommand { fn is_prev_next_window_variant(&self) -> bool { matches!( self, SwayrCommand::NextWindow { .. } | SwayrCommand::PrevWindow { .. } | SwayrCommand::NextTiledWindow { .. } | SwayrCommand::PrevTiledWindow { .. } | SwayrCommand::NextTabbedOrStackedWindow { .. } | SwayrCommand::PrevTabbedOrStackedWindow { .. } | SwayrCommand::NextFloatingWindow { .. } | SwayrCommand::PrevFloatingWindow { .. } | SwayrCommand::NextWindowOfSameLayout { .. } | SwayrCommand::PrevWindowOfSameLayout { .. } ) } } pub struct ExecSwayrCmdArgs<'a> { pub cmd: &'a SwayrCommand, pub extra_props: Arc>>, } impl DisplayFormat for SwayrCommand { fn format_for_display(&self, _: &cfg::Config) -> std::string::String { // TODO: It would be very nice if the display format was exactly like // the swayr invocation in the shell. Can that somehow be retrieved // from clap? format!("{:?}", self) } fn get_indent_level(&self) -> usize { 0 } } fn always_true(_x: &t::DisplayNode) -> bool { true } static IN_NEXT_PREV_WINDOW_SEQ: atomic::AtomicBool = atomic::AtomicBool::new(false); pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) { let props = args.extra_props; if args.cmd.is_prev_next_window_variant() { let before = IN_NEXT_PREV_WINDOW_SEQ.swap(true, atomic::Ordering::SeqCst); if !before { let mut map = props.write().unwrap(); for val in map.values_mut() { val.last_focus_time_for_next_prev_seq = val.last_focus_time; } } } else { IN_NEXT_PREV_WINDOW_SEQ.store(false, atomic::Ordering::SeqCst); } match args.cmd { SwayrCommand::SwitchToUrgentOrLRUWindow => { switch_to_urgent_or_lru_window(&*props.read().unwrap()) } SwayrCommand::SwitchWindow => switch_window(&*props.read().unwrap()), SwayrCommand::SwitchWorkspace => { switch_workspace(&*props.read().unwrap()) } SwayrCommand::SwitchOutput => switch_output(&*props.read().unwrap()), SwayrCommand::SwitchWorkspaceOrWindow => { switch_workspace_or_window(&*props.read().unwrap()) } SwayrCommand::SwitchWorkspaceContainerOrWindow => { switch_workspace_container_or_window(&*props.read().unwrap()) } SwayrCommand::QuitWindow => quit_window(&*props.read().unwrap()), SwayrCommand::QuitWorkspaceOrWindow => { quit_workspace_or_window(&*props.read().unwrap()) } SwayrCommand::QuitWorkspaceContainerOrWindow => { quit_workspace_container_or_window(&*props.read().unwrap()) } SwayrCommand::MoveFocusedToWorkspace => { move_focused_to_workspace(&*props.read().unwrap()) } SwayrCommand::MoveFocusedTo => move_focused_to(&*props.read().unwrap()), SwayrCommand::SwapFocusedWith => { swap_focused_with(&*props.read().unwrap()) } SwayrCommand::NextWindow { windows } => focus_window_in_direction( Direction::Forward, windows, &*props.read().unwrap(), Box::new(always_true), ), SwayrCommand::PrevWindow { windows } => focus_window_in_direction( Direction::Backward, windows, &*props.read().unwrap(), Box::new(always_true), ), SwayrCommand::NextTiledWindow { windows } => focus_window_in_direction( Direction::Forward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn.tree.is_child_of_tiled_container(dn.node.id) }), ), SwayrCommand::PrevTiledWindow { windows } => focus_window_in_direction( Direction::Backward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn.tree.is_child_of_tiled_container(dn.node.id) }), ), SwayrCommand::NextTabbedOrStackedWindow { windows } => { focus_window_in_direction( Direction::Forward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn .tree .is_child_of_tabbed_or_stacked_container(dn.node.id) }), ) } SwayrCommand::PrevTabbedOrStackedWindow { windows } => { focus_window_in_direction( Direction::Backward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| { !dn.node.is_floating() && dn .tree .is_child_of_tabbed_or_stacked_container(dn.node.id) }), ) } SwayrCommand::NextFloatingWindow { windows } => { focus_window_in_direction( Direction::Forward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), ) } SwayrCommand::PrevFloatingWindow { windows } => { focus_window_in_direction( Direction::Backward, windows, &*props.read().unwrap(), Box::new(|dn: &t::DisplayNode| dn.node.is_floating()), ) } SwayrCommand::NextWindowOfSameLayout { windows } => { focus_window_of_same_layout_in_direction( Direction::Forward, windows, &*props.read().unwrap(), ) } SwayrCommand::PrevWindowOfSameLayout { windows } => { focus_window_of_same_layout_in_direction( Direction::Backward, windows, &*props.read().unwrap(), ) } SwayrCommand::TileWorkspace { floating } => { tile_current_workspace(floating, false) } SwayrCommand::TabWorkspace { floating } => { tab_current_workspace(floating) } SwayrCommand::ShuffleTileWorkspace { floating } => { tile_current_workspace(floating, true) } SwayrCommand::ToggleTabShuffleTileWorkspace { floating } => { toggle_tab_tile_current_workspace(floating) } SwayrCommand::ConfigureOutputs => configure_outputs(), SwayrCommand::ExecuteSwaymsgCommand => exec_swaymsg_command(), SwayrCommand::ExecuteSwayrCommand => { let mut cmds = vec![ SwayrCommand::MoveFocusedToWorkspace, SwayrCommand::MoveFocusedTo, SwayrCommand::SwapFocusedWith, SwayrCommand::QuitWindow, SwayrCommand::QuitWorkspaceOrWindow, SwayrCommand::SwitchWindow, SwayrCommand::SwitchWorkspace, SwayrCommand::SwitchOutput, SwayrCommand::SwitchWorkspaceOrWindow, SwayrCommand::SwitchToUrgentOrLRUWindow, SwayrCommand::ConfigureOutputs, SwayrCommand::ExecuteSwaymsgCommand, ]; for f in [ ConsiderFloating::ExcludeFloating, ConsiderFloating::IncludeFloating, ] { cmds.push(SwayrCommand::ToggleTabShuffleTileWorkspace { floating: f.clone(), }); cmds.push(SwayrCommand::TileWorkspace { floating: f.clone(), }); cmds.push(SwayrCommand::TabWorkspace { floating: f.clone(), }); cmds.push(SwayrCommand::ShuffleTileWorkspace { floating: f.clone(), }); } for w in [ ConsiderWindows::AllWorkspaces, ConsiderWindows::CurrentWorkspace, ] { cmds.push(SwayrCommand::NextWindow { windows: w.clone() }); cmds.push(SwayrCommand::PrevWindow { windows: w.clone() }); cmds.push(SwayrCommand::NextTiledWindow { windows: w.clone() }); cmds.push(SwayrCommand::PrevTiledWindow { windows: w.clone() }); cmds.push(SwayrCommand::NextTabbedOrStackedWindow { windows: w.clone(), }); cmds.push(SwayrCommand::PrevTabbedOrStackedWindow { windows: w.clone(), }); cmds.push(SwayrCommand::NextFloatingWindow { windows: w.clone(), }); cmds.push(SwayrCommand::PrevFloatingWindow { windows: w.clone(), }) } if let Ok(c) = util::select_from_menu("Select swayr command", &cmds) { exec_swayr_cmd(ExecSwayrCmdArgs { cmd: c, extra_props: props, }); } } } } fn focus_window_by_id(id: i64) { run_sway_command(&[format!("[con_id={}]", id).as_str(), "focus"]); } fn quit_window_by_id(id: i64) { run_sway_command(&[format!("[con_id={}]", id).as_str(), "kill"]); } pub fn get_tree(include_scratchpad: bool) -> s::Node { match s::Connection::new() { Ok(mut con) => { let mut root = con.get_tree().expect("Got no root node"); if !include_scratchpad { root.nodes.retain(|o| !o.is_scratchpad()); } root } Err(err) => panic!("{}", err), } } pub fn get_outputs() -> Vec { match s::Connection::new() { Ok(mut con) => con.get_outputs().expect("Got no outputs"), Err(err) => panic!("{}", err), } } pub fn switch_to_urgent_or_lru_window( extra_props: &HashMap, ) { let root = get_tree(false); let tree = t::get_tree(&root, extra_props); if let Some(win) = tree.get_windows().get(0) { focus_window_by_id(win.node.id) } else { println!("No window to switch to.") } } lazy_static! { static ref DIGIT_AND_NAME: regex::Regex = regex::Regex::new(r"^(\d):(.*)").unwrap(); } fn create_workspace(ws_name: &str) { if DIGIT_AND_NAME.is_match(ws_name) { run_sway_command(&["workspace", "number", ws_name]); } else { run_sway_command(&["workspace", ws_name]); } } lazy_static! { static ref SPECIAL_WORKSPACE: regex::Regex = regex::Regex::new(r"^#*w:(.*)").unwrap(); static ref SPECIAL_SWAY: regex::Regex = regex::Regex::new(r"^#*s:(.*)").unwrap(); } fn chop_workspace_shortcut(input: &str) -> &str { match SPECIAL_WORKSPACE.captures(input) { Some(c) => c.get(1).unwrap().as_str(), None => input, } } fn chop_sway_shortcut(input: &str) -> &str { match SPECIAL_SWAY.captures(input) { Some(c) => c.get(1).unwrap().as_str(), None => input, } } fn handle_non_matching_input(input: &str) { if input.is_empty() { return; } if let Some(c) = SPECIAL_SWAY.captures(input) { run_sway_command(&c[1].split_ascii_whitespace().collect::>()); } else { let ws = chop_workspace_shortcut(input); create_workspace(ws); } } fn select_and_focus(prompt: &str, choices: &[t::DisplayNode]) { match util::select_from_menu(prompt, choices) { Ok(tn) => match tn.node.get_type() { t::Type::Output => { if !tn.node.is_scratchpad() { run_sway_command(&["focus output", tn.node.get_name()]); } } t::Type::Workspace => { if !tn.node.is_scratchpad() { run_sway_command(&["workspace", tn.node.get_name()]); } } t::Type::Window | t::Type::Container => { focus_window_by_id(tn.node.id) } t => { eprintln!("Cannot handle {:?} in select_and_focus", t) } }, Err(non_matching_input) => { handle_non_matching_input(&non_matching_input) } } } pub fn switch_window(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_focus("Select window", &tree.get_windows()); } pub fn switch_workspace(extra_props: &HashMap) { let root = get_tree(false); let tree = t::get_tree(&root, extra_props); select_and_focus("Select workspace", &tree.get_workspaces()); } pub fn switch_output(extra_props: &HashMap) { let root = get_tree(false); let tree = t::get_tree(&root, extra_props); select_and_focus("Select output", &tree.get_outputs()); } pub fn switch_workspace_or_window(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_focus( "Select workspace or window", &tree.get_workspaces_and_windows(), ); } pub fn switch_workspace_container_or_window( extra_props: &HashMap, ) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_focus( "Select workspace, container or window", &tree.get_workspaces_containers_and_windows(), ); } fn select_and_quit(prompt: &str, choices: &[t::DisplayNode]) { if let Ok(tn) = util::select_from_menu(prompt, choices) { match tn.node.get_type() { t::Type::Workspace | t::Type::Container => { for win in tn.node.iter().filter(|n| n.get_type() == t::Type::Window) { quit_window_by_id(win.id) } } t::Type::Window => quit_window_by_id(tn.node.id), t => { eprintln!("Cannot handle {:?} in quit_workspace_or_window", t) } } } } pub fn quit_window(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_quit("Quit window", &tree.get_windows()); } pub fn quit_workspace_or_window(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_quit( "Quit workspace or window", &tree.get_workspaces_and_windows(), ); } pub fn quit_workspace_container_or_window( extra_props: &HashMap, ) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_quit( "Quit workspace, container or window", &tree.get_workspaces_containers_and_windows(), ); } fn move_focused_to_workspace_1(ws_name: &str) { if DIGIT_AND_NAME.is_match(ws_name) { run_sway_command(&[ "move", "container", "to", "workspace", "number", ws_name, ]); } else { run_sway_command(&["move", "container", "to", "workspace", ws_name]); } } fn move_focused_to_container_or_window(id: i64) { run_sway_command(&[ &format!("[con_id=\"{}\"]", id), "mark", "--add", "__SWAYR_MOVE_TARGET__", ]); run_sway_command(&["move", "to", "mark", "__SWAYR_MOVE_TARGET__"]); run_sway_command(&["unmark", "__SWAYR_MOVE_TARGET__"]); } fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) { match util::select_from_menu(prompt, choices) { Ok(tn) => match tn.node.get_type() { t::Type::Output => { if tn.node.is_scratchpad() { run_sway_command_1("move container to scratchpad") } else { run_sway_command(&[ "move container to output", &tn.node.get_name(), ]) } } t::Type::Workspace => { if tn.node.is_scratchpad() { run_sway_command_1("move container to scratchpad") } else { move_focused_to_workspace_1(tn.node.get_name()) } } t::Type::Container | t::Type::Window => { move_focused_to_container_or_window(tn.node.id) } t => eprintln!("Cannot move focused to {:?}", t), }, Err(input) => { let ws_name = chop_workspace_shortcut(&input); move_focused_to_workspace_1(ws_name); } } } pub fn move_focused_to_workspace(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_move_focused_to( "Move focused container to workspace", &tree.get_workspaces(), ); } pub fn move_focused_to(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); select_and_move_focused_to( "Move focused container to workspace or container", &tree.get_outputs_workspaces_containers_and_windows(), ); } pub fn swap_focused_with(extra_props: &HashMap) { let root = get_tree(true); let tree = t::get_tree(&root, extra_props); match util::select_from_menu( "Swap focused with", &tree.get_workspaces_containers_and_windows(), ) { Ok(tn) => match tn.node.get_type() { t::Type::Workspace | t::Type::Container | t::Type::Window => { run_sway_command(&[ "swap", "container", "with", "con_id", &format!("{}", tn.node.id), ]) } t => eprintln!("Cannot move focused to {:?}", t), }, Err(input) => { let ws_name = chop_workspace_shortcut(&input); move_focused_to_workspace_1(ws_name); } } } pub enum Direction { Backward, Forward, } pub fn focus_window_in_direction( dir: Direction, consider_wins: &ConsiderWindows, extra_props: &HashMap, pred: Box bool>, ) { let root = get_tree(false); let tree = t::get_tree(&root, extra_props); let mut wins = tree.get_windows(); if consider_wins == &ConsiderWindows::CurrentWorkspace { let cur_ws = tree.get_current_workspace(); wins.retain(|w| { tree.get_parent_node_of_type(w.node.id, t::Type::Workspace) .unwrap() .id == cur_ws.id }); } wins.retain(pred); if wins.len() < 2 { return; } wins.sort_by(|a, b| { let lru_a = tree.last_focus_time_for_next_prev_seq(a.node.id); let lru_b = tree.last_focus_time_for_next_prev_seq(b.node.id); lru_a.cmp(&lru_b).reverse() }); let is_focused_window: Box bool> = if !wins.iter().any(|w| w.node.focused) { let last_focused_win_id = wins.get(0).unwrap().node.id; Box::new(move |dn| dn.node.id == last_focused_win_id) } else { Box::new(|dn| dn.node.focused) }; let mut iter: Box> = match dir { Direction::Forward => Box::new(wins.iter().rev().cycle()), Direction::Backward => Box::new(wins.iter().cycle()), }; loop { let win = iter.next().unwrap(); if is_focused_window(win) { let win = iter.next().unwrap(); focus_window_by_id(win.node.id); return; } } } pub fn focus_window_of_same_layout_in_direction( dir: Direction, consider_wins: &ConsiderWindows, extra_props: &HashMap, ) { let root = get_tree(false); let tree = t::get_tree(&root, extra_props); let wins = tree.get_windows(); let cur_win = wins.get(0); if let Some(cur_win) = cur_win { focus_window_in_direction( dir, consider_wins, extra_props, if cur_win.node.is_floating() { Box::new(|dn| dn.node.is_floating()) } else if !cur_win.node.is_floating() && cur_win .tree .is_child_of_tabbed_or_stacked_container(cur_win.node.id) { Box::new(|dn| { !dn.node.is_floating() && dn .tree .is_child_of_tabbed_or_stacked_container(dn.node.id) }) } else if !cur_win.node.is_floating() && cur_win.tree.is_child_of_tiled_container(cur_win.node.id) { Box::new(|dn| { !dn.node.is_floating() && dn.tree.is_child_of_tiled_container(dn.node.id) }) } else { Box::new(always_true) }, ) } } fn tile_current_workspace(floating: &ConsiderFloating, shuffle: bool) { match layout::relayout_current_workspace( floating == &ConsiderFloating::IncludeFloating, 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 { if win.is_floating() { con.run_command(format!( "[con_id={}] floating disable", win.id ))?; } std::thread::sleep(std::time::Duration::from_millis(25)); con.run_command(format!( "[con_id={}] move to workspace current", win.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.id))?; } } } Ok(()) }), ) { Ok(_) => (), Err(err) => eprintln!("Error retiling workspace: {:?}", err), } } fn tab_current_workspace(floating: &ConsiderFloating) { match layout::relayout_current_workspace( floating == &ConsiderFloating::IncludeFloating, 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 { if win.is_floating() { con.run_command(format!( "[con_id={}] floating disable", win.id ))?; } std::thread::sleep(std::time::Duration::from_millis(25)); con.run_command(format!( "[con_id={}] move to workspace current", win.id ))?; placed_wins.push(win); } Ok(()) }), ) { Ok(_) => (), Err(err) => eprintln!("Error retiling workspace: {:?}", err), } } fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) { let tree = get_tree(false); let workspaces = tree.nodes_of_type(t::Type::Workspace); let cur_ws = workspaces.iter().find(|w| w.is_current()).unwrap(); if cur_ws.layout == s::NodeLayout::Tabbed { tile_current_workspace(floating, true); } else { tab_current_workspace(floating); } } fn get_swaymsg_commands() -> Vec { let mut cmds: Vec = vec![]; for b in &["none", "normal", "csd", "pixel"] { cmds.push(format!["border {}", b]); } cmds.push("exit".to_string()); cmds.push("floating toggle".to_string()); cmds.push("focus child".to_string()); cmds.push("focus parent".to_string()); cmds.push("focus tiling".to_string()); cmds.push("focus floating".to_string()); cmds.push("focus mode_toggle".to_string()); cmds.push("fullscreen toggle".to_string()); cmds.push("reload".to_string()); cmds.push("sticky toggle".to_string()); cmds.push("kill".to_string()); cmds.push("tiling_drag toggle".to_string()); for x in &["focus", "fullscreen", "open", "none", "visible"] { cmds.push(format!["inhibit_idle {}", x]) } for l in &["default", "splith", "splitv", "stacking", "tiling"] { cmds.push(format!["layout {}", l]) } for e in &["enable", "disable"] { cmds.push(format!["shortcuts_inhibitor {}", e]) } for x in &["yes", "no", "always"] { cmds.push(format!["focus_follows_mouse {}", x]) } for x in &["smart", "urgent", "focus", "none"] { cmds.push(format!["focus_on_window_activation {}", x]) } for x in &["yes", "no", "force", "workspace"] { cmds.push(format!["focus_wrapping {}", x]) } for x in &[ "none", "vertical", "horizontal", "both", "smart", "smart_no_gaps", ] { cmds.push(format!["hide_edge_borders {}", x]) } for x in &["on", "no_gaps", "off"] { cmds.push(format!["smart_borders {}", x]) } for x in &["on", "off"] { cmds.push(format!["smart_gaps {}", x]) } for x in &["output", "container", "none"] { cmds.push(format!["mouse_warping {}", x]) } for x in &["smart", "ignore", "leave_fullscreen"] { cmds.push(format!["popup_during_fullscreen {}", x]) } for x in &["yes", "no"] { cmds.push(format!["show_marks {}", x]); cmds.push(format!["workspace_auto_back_and_forth {}", x]); } for x in &["left", "center", "right"] { cmds.push(format!["title_align {}", x]); } for x in &["enable", "disable", "allow", "deny"] { cmds.push(format!["urgent {}", x]) } cmds.sort(); cmds.into_iter().map(|c| SwaymsgCmd { cmd: c }).collect() } struct SwaymsgCmd { cmd: String, } impl DisplayFormat for SwaymsgCmd { fn format_for_display(&self, _: &cfg::Config) -> std::string::String { self.cmd.clone() } fn get_indent_level(&self) -> usize { 0 } } pub fn exec_swaymsg_command() { let cmds = get_swaymsg_commands(); let cmd = util::select_from_menu("Execute swaymsg command", &cmds); match cmd { Ok(cmd) => run_sway_command_1(&cmd.cmd), Err(cmd) if !cmd.is_empty() => { let cmd = chop_sway_shortcut(&cmd); run_sway_command_1(cmd); } Err(_) => (), } } pub fn configure_outputs() { let outputs = get_outputs(); let mut cmds = vec![]; for o in outputs { cmds.push(format!("output {} toggle", o.name)); for mode in o.modes { cmds.push(format!( "output {} mode {}x{}", o.name, mode.width, mode.height )); } for on_off in ["on", "off"] { cmds.push(format!("output {} dpms {}", o.name, on_off)); cmds.push(format!("output {} adaptive_sync {}", o.name, on_off)); } for transform in [ "normal", "90", "180", "270", "360", "flipped", "flipped-90", "flipped-180", "flipped-270", ] { for dir in ["clockwise", "anticlockwise"] { cmds.push(format!( "output {} transform {} {}", o.name, transform, dir )); } } for pix in ["rgb", "bgr", "vrbg", "vbgr", "none"] { cmds.push(format!("output {} subpixel {}", o.name, pix)); } for pix in ["linear", "nearest", "smart"] { cmds.push(format!("output {} scale_filter {}", o.name, pix)); } } cmds.sort(); let cmds: Vec = cmds.into_iter().map(|c| SwaymsgCmd { cmd: c }).collect(); while let Ok(cmd) = util::select_from_menu("Output command", &cmds) { run_sway_command_1(&cmd.cmd); } }