Alternative, light-weight con rewrite

timeout_old
Tassilo Horn 3 years ago
parent 125d2ed4b0
commit 566a27a9a6
  1. 262
      src/cmds.rs
  2. 664
      src/con.rs
  3. 32
      src/layout.rs

@ -172,7 +172,7 @@ impl DisplayFormat for SwayrCommand {
} }
} }
fn always_true(_x: &con::Window) -> bool { fn always_true(_x: &con::DisplayNode) -> bool {
true true
} }
@ -216,16 +216,18 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| { Box::new(|dn: &con::DisplayNode| {
!w.is_floating() && w.is_child_of_tiled_container() !dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id)
}), }),
), ),
SwayrCommand::PrevTiledWindow { windows } => focus_window_in_direction( SwayrCommand::PrevTiledWindow { windows } => focus_window_in_direction(
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| { Box::new(|dn: &con::DisplayNode| {
!w.is_floating() && w.is_child_of_tiled_container() !dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id)
}), }),
), ),
SwayrCommand::NextTabbedOrStackedWindow { windows } => { SwayrCommand::NextTabbedOrStackedWindow { windows } => {
@ -233,9 +235,11 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| { Box::new(|dn: &con::DisplayNode| {
!w.is_floating() !dn.node.is_floating()
&& w.is_child_of_tabbed_or_stacked_container() && dn
.tree
.is_child_of_tabbed_or_stacked_container(dn.node.id)
}), }),
) )
} }
@ -244,9 +248,11 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| { Box::new(|dn: &con::DisplayNode| {
!w.is_floating() !dn.node.is_floating()
&& w.is_child_of_tabbed_or_stacked_container() && dn
.tree
.is_child_of_tabbed_or_stacked_container(dn.node.id)
}), }),
) )
} }
@ -255,7 +261,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Forward, Direction::Forward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| w.is_floating()), Box::new(|dn: &con::DisplayNode| dn.node.is_floating()),
) )
} }
SwayrCommand::PrevFloatingWindow { windows } => { SwayrCommand::PrevFloatingWindow { windows } => {
@ -263,7 +269,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Backward, Direction::Backward,
windows, windows,
&*props.read().unwrap(), &*props.read().unwrap(),
Box::new(|w: &con::Window| w.is_floating()), Box::new(|dn: &con::DisplayNode| dn.node.is_floating()),
) )
} }
SwayrCommand::NextWindowOfSameLayout { windows } => { SwayrCommand::NextWindowOfSameLayout { windows } => {
@ -375,25 +381,39 @@ fn quit_window_by_id(id: i64) {
run_sway_command(&[format!("[con_id={}]", id).as_str(), "kill"]); run_sway_command(&[format!("[con_id={}]", id).as_str(), "kill"]);
} }
pub fn get_tree() -> s::Node { #[deprecated]
pub fn get_tree_old() -> s::Node {
match s::Connection::new() { match s::Connection::new() {
Ok(mut con) => con.get_tree().expect("Got no root node"), Ok(mut con) => con.get_tree().expect("Got no root node"),
Err(err) => panic!("{}", err), Err(err) => panic!("{}", err),
} }
} }
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 switch_to_urgent_or_lru_window( pub fn switch_to_urgent_or_lru_window(
extra_props: &HashMap<i64, con::ExtraProps>, extra_props: &HashMap<i64, con::ExtraProps>,
) { ) {
let root = get_tree(); let root = get_tree(false);
let windows = con::get_windows(&root, false, extra_props); let tree = con::get_tree(&root, extra_props);
if let Some(win) = windows if let Some(win) = tree.get_windows().get(0) {
.iter() println!(
.find(|w| w.is_urgent()) "Switching to {}, id: {}",
.or_else(|| windows.get(0)) win.node.get_app_name(),
{ win.node.id
println!("Switching to {}, id: {}", win.get_app_name(), win.get_id()); );
focus_window_by_id(win.get_id()) focus_window_by_id(win.node.id)
} else { } else {
println!("No window to switch to.") println!("No window to switch to.")
} }
@ -447,11 +467,11 @@ fn handle_non_matching_input(input: &str) {
} }
pub fn switch_window(extra_props: &HashMap<i64, con::ExtraProps>) { pub fn switch_window(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree(); let root = get_tree(true);
let windows = con::get_windows(&root, true, extra_props); let tree = con::get_tree(&root, extra_props);
match util::select_from_menu("Switch to window", &windows) { match util::select_from_menu("Switch to window", &tree.get_windows()) {
Ok(window) => focus_window_by_id(window.get_id()), Ok(window) => focus_window_by_id(window.node.id),
Err(non_matching_input) => { Err(non_matching_input) => {
handle_non_matching_input(&non_matching_input) handle_non_matching_input(&non_matching_input)
} }
@ -467,54 +487,49 @@ pub fn focus_window_in_direction(
dir: Direction, dir: Direction,
consider_wins: &ConsiderWindows, consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, con::ExtraProps>, extra_props: &HashMap<i64, con::ExtraProps>,
pred: Box<dyn Fn(&con::Window) -> bool>, pred: Box<dyn Fn(&con::DisplayNode) -> bool>,
) { ) {
let root = get_tree(); let root = get_tree(false);
let root = match consider_wins { let tree = con::get_tree(&root, extra_props);
ConsiderWindows::AllWorkspaces => &root, let mut wins = tree.get_windows();
ConsiderWindows::CurrentWorkspace => {
con::get_workspaces(&root, false, extra_props) if consider_wins == &ConsiderWindows::CurrentWorkspace {
.iter() let cur_ws = tree.get_current_workspace();
.find(|w| w.is_current()) wins.retain(|w| {
.expect("No current workspace!") tree.get_workspace_node(w.node.id).unwrap().id == cur_ws.id
.node });
} }
};
let mut windows: Vec<con::Window> = wins.retain(pred);
con::get_windows(root, false, extra_props)
.into_iter()
.filter(|w| pred(w))
.collect();
if windows.len() < 2 { if wins.len() < 2 {
return; return;
} }
windows.sort_by(|a, b| { wins.sort_by(|a, b| {
let lru_a = a.last_focus_time_for_next_prev_seq(); let lru_a = tree.last_focus_time_for_next_prev_seq(a.node.id);
let lru_b = b.last_focus_time_for_next_prev_seq(); let lru_b = tree.last_focus_time_for_next_prev_seq(b.node.id);
lru_a.cmp(&lru_b).reverse() lru_a.cmp(&lru_b).reverse()
}); });
let is_focused_window: Box<dyn Fn(&con::Window) -> bool> = let is_focused_window: Box<dyn Fn(&con::DisplayNode) -> bool> =
if !windows.iter().any(|w| w.is_focused()) { if !wins.iter().any(|w| w.node.focused) {
let last_focused_win_id = windows.get(0).unwrap().get_id(); let last_focused_win_id = wins.get(0).unwrap().node.id;
Box::new(move |w| w.get_id() == last_focused_win_id) Box::new(move |dn| dn.node.id == last_focused_win_id)
} else { } else {
Box::new(|w: &con::Window| w.is_focused()) Box::new(|dn| dn.node.focused)
}; };
let mut iter: Box<dyn Iterator<Item = &con::Window>> = match dir { let mut iter: Box<dyn Iterator<Item = &con::DisplayNode>> = match dir {
Direction::Forward => Box::new(windows.iter().rev().cycle()), Direction::Forward => Box::new(wins.iter().rev().cycle()),
Direction::Backward => Box::new(windows.iter().cycle()), Direction::Backward => Box::new(wins.iter().cycle()),
}; };
loop { loop {
let win = iter.next().unwrap(); let win = iter.next().unwrap();
if is_focused_window(win) { if is_focused_window(win) {
let win = iter.next().unwrap(); let win = iter.next().unwrap();
focus_window_by_id(win.get_id()); focus_window_by_id(win.node.id);
return; return;
} }
} }
@ -525,32 +540,35 @@ pub fn focus_window_of_same_layout_in_direction(
consider_wins: &ConsiderWindows, consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, con::ExtraProps>, extra_props: &HashMap<i64, con::ExtraProps>,
) { ) {
let root = get_tree(); let root = get_tree(false);
let windows = con::get_windows(&root, false, extra_props); let tree = con::get_tree(&root, extra_props);
let current_window = windows let wins = tree.get_windows();
.iter() let cur_win = wins.get(0);
.find(|w| w.is_focused())
.or_else(|| windows.get(0)); if let Some(cur_win) = cur_win {
if let Some(current_window) = current_window {
focus_window_in_direction( focus_window_in_direction(
dir, dir,
consider_wins, consider_wins,
extra_props, extra_props,
if current_window.is_floating() { if cur_win.node.is_floating() {
Box::new(|w| w.is_floating()) Box::new(|dn| dn.node.is_floating())
} else if !current_window.is_floating() } else if !cur_win.node.is_floating()
&& current_window.is_child_of_tabbed_or_stacked_container() && cur_win
.tree
.is_child_of_tabbed_or_stacked_container(cur_win.node.id)
{ {
Box::new(|w| { Box::new(|dn| {
!w.is_floating() !dn.node.is_floating()
&& w.is_child_of_tabbed_or_stacked_container() && dn
.tree
.is_child_of_tabbed_or_stacked_container(dn.node.id)
}) })
} else if !current_window.is_floating() } else if !cur_win.node.is_floating()
&& current_window.is_child_of_tiled_container() && cur_win.tree.is_child_of_tiled_container(cur_win.node.id)
{ {
Box::new(|w| { Box::new(|dn| {
!w.is_floating() && w.is_child_of_tiled_container() !dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id)
}) })
} else { } else {
Box::new(always_true) Box::new(always_true)
@ -560,11 +578,14 @@ pub fn focus_window_of_same_layout_in_direction(
} }
pub fn switch_workspace(extra_props: &HashMap<i64, con::ExtraProps>) { pub fn switch_workspace(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree(); let root = get_tree(false);
let workspaces = con::get_workspaces(&root, false, extra_props); let tree = con::get_tree(&root, extra_props);
match util::select_from_menu("Switch to workspace", &workspaces) { match util::select_from_menu("Switch to workspace", &tree.get_workspaces())
Ok(workspace) => run_sway_command(&["workspace", workspace.get_name()]), {
Ok(workspace) => {
run_sway_command(&["workspace", workspace.node.get_name()])
}
Err(non_matching_input) => { Err(non_matching_input) => {
handle_non_matching_input(&non_matching_input) handle_non_matching_input(&non_matching_input)
} }
@ -574,15 +595,16 @@ pub fn switch_workspace(extra_props: &HashMap<i64, con::ExtraProps>) {
pub fn move_focused_container_to_workspace( pub fn move_focused_container_to_workspace(
extra_props: &HashMap<i64, con::ExtraProps>, extra_props: &HashMap<i64, con::ExtraProps>,
) { ) {
let root = get_tree(); let root = get_tree(true);
let workspaces = con::get_workspaces(&root, false, extra_props); let tree = con::get_tree(&root, extra_props);
let workspaces = tree.get_workspaces();
let val = util::select_from_menu( let val = util::select_from_menu(
"Move focused container to workspace", "Move focused container to workspace",
&workspaces, &workspaces,
); );
let ws_name = &match val { let ws_name = &match val {
Ok(workspace) => String::from(workspace.get_name()), Ok(workspace) => String::from(workspace.node.get_name()),
Err(input) => String::from(chop_workspace_shortcut(&input)), Err(input) => String::from(chop_workspace_shortcut(&input)),
}; };
@ -601,15 +623,20 @@ pub fn move_focused_container_to_workspace(
} }
pub fn switch_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) { pub fn switch_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree(); let root = get_tree(true);
let workspaces = con::get_workspaces(&root, true, extra_props); let tree = con::get_tree(&root, extra_props);
let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); let ws_or_wins = tree.get_workspaces_and_windows();
match util::select_from_menu("Select workspace or window", &ws_or_wins) { match util::select_from_menu("Select workspace or window", &ws_or_wins) {
Ok(ws_or_win) => match ws_or_win { Ok(tn) => match tn.node.get_type() {
con::WsOrWin::Ws { ws } => { con::Type::Workspace => {
run_sway_command(&["workspace", ws.get_name()]); if !tn.node.is_scratchpad() {
run_sway_command(&["workspace", tn.node.get_name()]);
}
}
con::Type::Window => focus_window_by_id(tn.node.id),
t => {
eprintln!("Cannot handle {:?} in switch_workspace_or_window", t)
} }
con::WsOrWin::Win { win } => focus_window_by_id(win.get_id()),
}, },
Err(non_matching_input) => { Err(non_matching_input) => {
handle_non_matching_input(&non_matching_input) handle_non_matching_input(&non_matching_input)
@ -618,28 +645,35 @@ pub fn switch_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) {
} }
pub fn quit_window(extra_props: &HashMap<i64, con::ExtraProps>) { pub fn quit_window(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree(); let root = get_tree(true);
let windows = con::get_windows(&root, true, extra_props); let tree = con::get_tree(&root, extra_props);
if let Ok(window) = util::select_from_menu("Quit window", &windows) { if let Ok(window) =
quit_window_by_id(window.get_id()) util::select_from_menu("Quit window", &tree.get_windows())
{
quit_window_by_id(window.node.id)
} }
} }
pub fn quit_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) { pub fn quit_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree(); let root = get_tree(true);
let workspaces = con::get_workspaces(&root, true, extra_props); let tree = con::get_tree(&root, extra_props);
let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); let ws_or_wins = tree.get_workspaces_and_windows();
if let Ok(ws_or_win) = if let Ok(tn) =
util::select_from_menu("Quit workspace or window", &ws_or_wins) util::select_from_menu("Quit workspace or window", &ws_or_wins)
{ {
match ws_or_win { match tn.node.get_type() {
con::WsOrWin::Ws { ws } => { con::Type::Workspace => {
for win in ws.get_windows() { for win in
quit_window_by_id(win.get_id()) tn.node.iter().filter(|n| n.get_type() == con::Type::Window)
{
quit_window_by_id(win.id)
} }
} }
con::WsOrWin::Win { win } => quit_window_by_id(win.get_id()), con::Type::Window => quit_window_by_id(tn.node.id),
t => {
eprintln!("Cannot handle {:?} in quit_workspace_or_window", t)
}
} }
} }
} }
@ -662,22 +696,19 @@ fn tile_current_workspace(floating: &ConsiderFloating, shuffle: bool) {
if win.is_floating() { if win.is_floating() {
con.run_command(format!( con.run_command(format!(
"[con_id={}] floating disable", "[con_id={}] floating disable",
win.get_id() win.id
))?; ))?;
} }
std::thread::sleep(std::time::Duration::from_millis(25)); std::thread::sleep(std::time::Duration::from_millis(25));
con.run_command(format!( con.run_command(format!(
"[con_id={}] move to workspace current", "[con_id={}] move to workspace current",
win.get_id() win.id
))?; ))?;
placed_wins.push(win); placed_wins.push(win);
if shuffle { if shuffle {
std::thread::sleep(std::time::Duration::from_millis(25)); std::thread::sleep(std::time::Duration::from_millis(25));
if let Some(win) = placed_wins.choose(&mut rng) { if let Some(win) = placed_wins.choose(&mut rng) {
con.run_command(format!( con.run_command(format!("[con_id={}] focus", win.id))?;
"[con_id={}] focus",
win.get_id()
))?;
} }
} }
} }
@ -702,14 +733,14 @@ fn tab_current_workspace(floating: &ConsiderFloating) {
if win.is_floating() { if win.is_floating() {
con.run_command(format!( con.run_command(format!(
"[con_id={}] floating disable", "[con_id={}] floating disable",
win.get_id() win.id
))?; ))?;
} }
std::thread::sleep(std::time::Duration::from_millis(25)); std::thread::sleep(std::time::Duration::from_millis(25));
con.run_command(format!( con.run_command(format!(
"[con_id={}] move to workspace current", "[con_id={}] move to workspace current",
win.get_id() win.id
))?; ))?;
placed_wins.push(win); placed_wins.push(win);
} }
@ -722,12 +753,9 @@ fn tab_current_workspace(floating: &ConsiderFloating) {
} }
fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) { fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) {
let tree = get_tree(); let tree = get_tree(false);
let workspaces = tree.workspaces(); let workspaces = tree.nodes_of_type(con::Type::Workspace);
let cur_ws = workspaces let cur_ws = workspaces.iter().find(|w| w.is_current()).unwrap();
.iter()
.find(|w| con::is_current_container(w))
.unwrap();
if cur_ws.layout == s::NodeLayout::Tabbed { if cur_ws.layout == s::NodeLayout::Tabbed {
tile_current_workspace(floating, true); tile_current_workspace(floating, true);
} else { } else {

@ -15,7 +15,7 @@
//! Convenience data structures built from the IPC structs. //! Convenience data structures built from the IPC structs.
use crate::config as cfg; use crate::config;
use crate::util; use crate::util;
use crate::util::DisplayFormat; use crate::util::DisplayFormat;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -55,28 +55,37 @@ impl<'a> Iterator for NodeIter<'a> {
} }
} }
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Type {
Root,
Output,
Workspace,
Container,
Window,
}
/// Extension methods for [`swayipc::Node`]. /// Extension methods for [`swayipc::Node`].
pub trait NodeMethods { pub trait NodeMethods {
/// Returns an iterator for this [`swayipc::Node`] and its childres. /// Returns an iterator for this [`swayipc::Node`] and its childres.
fn iter(&self) -> NodeIter; fn iter(&self) -> NodeIter;
fn is_window(&self) -> bool; /// Returns true if this node is an output.
fn get_type(&self) -> Type;
/// Either a workspace or a con holding windows, e.g. a vertical split side
/// in a horizontally split workspace.
fn is_container(&self) -> bool;
/// Returns all nodes being application windows. /// Returns the app_id if present, otherwise the window-properties class if
fn windows(&self) -> Vec<&s::Node>; /// present, otherwise "<unknown_app>".
fn get_app_name(&self) -> &str;
/// Returns all nodes being containers. fn nodes_of_type(&self, t: Type) -> Vec<&s::Node>;
fn containers(&self) -> Vec<&s::Node>; fn get_name(&self) -> &str;
/// Returns all nodes being workspaces. // Returns true if this node is the scratchpad output or workspace.
fn workspaces(&self) -> Vec<&s::Node>;
// Returns true if this node is the scratchpad workspace.
fn is_scratchpad(&self) -> bool; fn is_scratchpad(&self) -> bool;
fn is_floating(&self) -> bool;
fn is_current(&self) -> bool {
self.iter().any(|n| n.focused)
}
} }
impl NodeMethods for s::Node { impl NodeMethods for s::Node {
@ -84,34 +93,66 @@ impl NodeMethods for s::Node {
NodeIter::new(self) NodeIter::new(self)
} }
fn is_window(&self) -> bool { fn get_type(&self) -> Type {
(self.node_type == s::NodeType::Con match self.node_type {
|| self.node_type == s::NodeType::FloatingCon) s::NodeType::Root => Type::Root,
&& self.name.is_some() s::NodeType::Output => Type::Output,
s::NodeType::Workspace => Type::Workspace,
s::NodeType::FloatingCon => Type::Window,
_ => {
if self.node_type == s::NodeType::Con
&& self.name.is_none()
&& self.layout != s::NodeLayout::None
{
Type::Container
} else if (self.node_type == s::NodeType::Con
|| self.node_type == s::NodeType::FloatingCon)
&& self.name.is_some()
{
Type::Window
} else {
panic!(
"Don't know type of node with id {} and node_type {:?}",
self.id, self.node_type
)
}
}
}
} }
fn is_container(&self) -> bool { fn get_name(&self) -> &str {
self.node_type == s::NodeType::Con if let Some(name) = &self.name {
&& self.name.is_none() name.as_ref()
&& self.layout != s::NodeLayout::None } else {
"<unnamed>"
}
} }
fn windows(&self) -> Vec<&s::Node> { fn get_app_name(&self) -> &str {
self.iter().filter(|n| n.is_window()).collect() if let Some(app_id) = &self.app_id {
app_id
} else if let Some(wp_class) = self
.window_properties
.as_ref()
.and_then(|wp| wp.class.as_ref())
{
wp_class
} else {
"<unknown_app>"
}
} }
fn containers(&self) -> Vec<&s::Node> { fn is_scratchpad(&self) -> bool {
self.iter().filter(|n| n.is_container()).collect() let name = self.get_name();
name.eq("__i3") || name.eq("__i3_scratch")
} }
fn workspaces(&self) -> Vec<&s::Node> { fn nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
self.iter() self.iter().filter(|n| n.get_type() == t).collect()
.filter(|n| n.node_type == s::NodeType::Workspace)
.collect()
} }
fn is_scratchpad(&self) -> bool { fn is_floating(&self) -> bool {
self.name.is_some() && self.name.as_ref().unwrap().eq("__i3_scratch") self.node_type == s::NodeType::FloatingCon
} }
} }
@ -123,386 +164,295 @@ pub struct ExtraProps {
pub last_focus_time_for_next_prev_seq: u128, pub last_focus_time_for_next_prev_seq: u128,
} }
#[derive(Debug)] pub struct Tree<'a> {
pub struct Window<'a> { root: &'a s::Node,
node: &'a s::Node, id_node: HashMap<i64, &'a s::Node>,
workspace: &'a s::Node, id_parent: HashMap<i64, i64>,
extra_props: Option<ExtraProps>, extra_props: &'a HashMap<i64, ExtraProps>,
} }
impl Window<'_> { pub struct DisplayNode<'a> {
pub fn get_id(&self) -> i64 { pub node: &'a s::Node,
self.node.id pub tree: &'a Tree<'a>,
} }
pub fn get_app_name(&self) -> &str {
if let Some(app_id) = &self.node.app_id {
app_id
} else if let Some(wp_class) = self
.node
.window_properties
.as_ref()
.and_then(|wp| wp.class.as_ref())
{
wp_class
} else {
"<Unknown>"
}
}
pub fn get_title(&self) -> &str {
self.node.name.as_ref().unwrap()
}
pub fn is_urgent(&self) -> bool {
self.node.urgent
}
pub fn is_focused(&self) -> bool {
self.node.focused
}
pub fn is_floating(&self) -> bool { impl<'a> Tree<'a> {
self.node.node_type == s::NodeType::FloatingCon fn get_node_by_id(&self, id: i64) -> &&s::Node {
self.id_node
.get(&id)
.unwrap_or_else(|| panic!("No node with id {}", id))
} }
pub fn get_parent(&self) -> &s::Node { fn get_parent_node(&self, id: i64) -> Option<&&s::Node> {
NodeIter::new(self.workspace) self.id_parent.get(&id).map(|pid| self.get_node_by_id(*pid))
.find(|n| {
n.nodes.contains(self.node)
|| n.floating_nodes.contains(self.node)
})
.unwrap_or_else(|| panic!("Window {:?} has no parent node!", self))
} }
pub fn is_child_of_tiled_container(&self) -> bool { pub fn get_workspace_node(&self, id: i64) -> Option<&&s::Node> {
let layout = &self.get_parent().layout; let n = self.get_node_by_id(id);
layout == &s::NodeLayout::SplitH || layout == &s::NodeLayout::SplitV if n.get_type() == Type::Workspace {
Some(n)
} else if let Some(pid) = self.id_parent.get(&id) {
self.get_workspace_node(*pid)
} else {
None
}
} }
pub fn is_child_of_tabbed_or_stacked_container(&self) -> bool { pub fn last_focus_time(&self, id: i64) -> u128 {
let layout = &self.get_parent().layout; self.extra_props.get(&id).map_or(0, |wp| wp.last_focus_time)
layout == &s::NodeLayout::Tabbed || layout == &s::NodeLayout::Stacked
} }
pub fn last_focus_time_for_next_prev_seq(&self) -> u128 { pub fn last_focus_time_for_next_prev_seq(&self, id: i64) -> u128 {
self.extra_props self.extra_props
.as_ref() .get(&id)
.map_or(0, |wp| wp.last_focus_time_for_next_prev_seq) .map_or(0, |wp| wp.last_focus_time_for_next_prev_seq)
} }
}
impl PartialEq for Window<'_> {
fn eq(&self, other: &Self) -> bool {
self.get_id() == other.get_id()
}
}
impl Eq for Window<'_> {} fn sorted_nodes_of_type_1(
&self,
impl Ord for Window<'_> { node: &'a s::Node,
fn cmp(&self, other: &Self) -> cmp::Ordering { t: Type,
if self == other { ) -> Vec<&s::Node> {
cmp::Ordering::Equal let mut v: Vec<&s::Node> = node.nodes_of_type(t);
} else if self.is_urgent() && !other.is_urgent() v.sort_by(|a, b| {
|| !self.is_focused() && other.is_focused() if a.urgent && !b.urgent {
{ cmp::Ordering::Less
cmp::Ordering::Less } else if !a.urgent && b.urgent {
} else if !self.is_urgent() && other.is_urgent() cmp::Ordering::Greater
|| self.is_focused() && !other.is_focused() } else {
{ let lru_a = self.last_focus_time(a.id);
std::cmp::Ordering::Greater let lru_b = self.last_focus_time(b.id);
} else { lru_a.cmp(&lru_b).reverse()
let lru_a = }
self.extra_props.as_ref().map_or(0, |wp| wp.last_focus_time); });
let lru_b = other v
.extra_props
.as_ref()
.map_or(0, |wp| wp.last_focus_time);
lru_a.cmp(&lru_b).reverse()
}
}
}
impl PartialOrd for Window<'_> {
fn partial_cmp(&self, other: &Window) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
lazy_static! {
static ref APP_NAME_AND_VERSION_RX: regex::Regex =
regex::Regex::new("(.+)(-[0-9.]+)").unwrap();
}
fn maybe_html_escape(do_it: bool, text: &str) -> String {
if do_it {
text.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("&", "&amp;")
} else {
text.to_string()
} }
}
impl<'a> DisplayFormat for Window<'a> { fn sorted_nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
fn format_for_display(&self, cfg: &cfg::Config) -> String { self.sorted_nodes_of_type_1(self.root, t)
let window_format = cfg.get_format_window_format();
let urgency_start = cfg.get_format_urgency_start();
let urgency_end = cfg.get_format_urgency_end();
let html_escape = cfg.get_format_html_escape();
let icon_dirs = cfg.get_format_icon_dirs();
// fallback_icon has no default value.
let fallback_icon: Option<Box<std::path::Path>> = cfg
.get_format_fallback_icon()
.as_ref()
.map(|i| std::path::Path::new(i).to_owned().into_boxed_path());
// Some apps report, e.g., Gimp-2.10 but the icon is still named
// gimp.png.
let app_name_no_version =
APP_NAME_AND_VERSION_RX.replace(self.get_app_name(), "$1");
window_format
.replace("{id}", format!("{}", self.get_id()).as_str())
.replace(
"{urgency_start}",
if self.is_urgent() {
urgency_start.as_str()
} else {
""
},
)
.replace(
"{urgency_end}",
if self.is_urgent() {
urgency_end.as_str()
} else {
""
},
)
.replace(
"{app_name}",
&maybe_html_escape(html_escape, self.get_app_name()),
)
.replace(
"{workspace_name}",
&maybe_html_escape(
html_escape,
self.workspace.name.as_ref().unwrap().as_str(),
),
)
.replace(
"{marks}",
&maybe_html_escape(html_escape, &self.node.marks.join(", ")),
)
.replace(
"{app_icon}",
util::get_icon(self.get_app_name(), &icon_dirs)
.or_else(|| {
util::get_icon(&app_name_no_version, &icon_dirs)
})
.or_else(|| {
util::get_icon(
&app_name_no_version.to_lowercase(),
&icon_dirs,
)
})
.or(fallback_icon)
.map(|i| i.to_string_lossy().into_owned())
.unwrap_or_else(String::new)
.as_str(),
)
.replace(
"{title}",
&maybe_html_escape(html_escape, self.get_title()),
)
} }
}
fn build_windows<'a>( fn as_display_nodes(&self, v: Vec<&'a s::Node>) -> Vec<DisplayNode> {
root: &'a s::Node, v.iter()
include_scratchpad_windows: bool, .map(|n| DisplayNode {
extra_props: &HashMap<i64, ExtraProps>,
) -> Vec<Window<'a>> {
let mut v = vec![];
for workspace in root.workspaces() {
if !include_scratchpad_windows && workspace.is_scratchpad() {
continue;
}
for n in workspace.windows() {
v.push(Window {
node: n, node: n,
extra_props: extra_props.get(&n.id).cloned(), tree: self,
workspace,
}) })
} .collect()
} }
v
}
fn build_workspaces<'a>( pub fn get_current_workspace(&self) -> &s::Node {
root: &'a s::Node, self.root
include_scratchpad: bool,
extra_props: &HashMap<i64, ExtraProps>,
) -> Vec<Workspace<'a>> {
let mut v = vec![];
for workspace in root.workspaces() {
if workspace.is_scratchpad() && !include_scratchpad {
continue;
}
let mut wins: Vec<Window> = workspace
.windows()
.iter() .iter()
.map(|w| Window { .find(|n| n.get_type() == Type::Workspace && n.is_current())
node: w, .expect("No current Workspace")
extra_props: extra_props.get(&w.id).cloned(),
workspace,
})
.collect();
if !extra_props.is_empty() {
wins.sort();
}
v.push(Workspace {
node: workspace,
extra_props: extra_props.get(&workspace.id).cloned(),
windows: wins,
})
}
if !extra_props.is_empty() {
v.sort();
} }
v
}
/// Gets all application windows of the tree. pub fn get_workspaces(&self) -> Vec<DisplayNode> {
pub fn get_windows<'a>( let mut v = self.sorted_nodes_of_type(Type::Workspace);
root: &'a s::Node, v.rotate_left(1);
include_scratchpad_windows: bool, self.as_display_nodes(v)
extra_props: &HashMap<i64, ExtraProps>,
) -> Vec<Window<'a>> {
let mut wins = build_windows(root, include_scratchpad_windows, extra_props);
if !extra_props.is_empty() {
wins.sort();
} }
wins
}
/// Gets all workspaces of the tree.
pub fn get_workspaces<'a>(
root: &'a s::Node,
include_scratchpad: bool,
extra_props: &HashMap<i64, ExtraProps>,
) -> Vec<Workspace<'a>> {
let mut workspaces =
build_workspaces(root, include_scratchpad, extra_props);
workspaces.rotate_left(1);
workspaces
}
pub enum WsOrWin<'a> { pub fn get_windows(&self) -> Vec<DisplayNode> {
Ws { ws: &'a Workspace<'a> }, let mut v = self.sorted_nodes_of_type(Type::Window);
Win { win: &'a Window<'a> }, v.rotate_left(1);
} self.as_display_nodes(v)
impl DisplayFormat for WsOrWin<'_> {
fn format_for_display(&self, cfg: &cfg::Config) -> String {
match self {
WsOrWin::Ws { ws } => ws.format_for_display(cfg),
WsOrWin::Win { win } => win.format_for_display(cfg),
}
} }
}
impl WsOrWin<'_> { pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> {
pub fn from_workspaces<'a>( let workspaces = self.sorted_nodes_of_type(Type::Workspace);
workspaces: &'a [Workspace], let mut first = true;
) -> Vec<WsOrWin<'a>> {
let mut v = vec![]; let mut v = vec![];
for ws in workspaces { for ws in workspaces {
v.push(WsOrWin::Ws { ws }); v.push(ws);
for win in &ws.windows { let mut wins = self.sorted_nodes_of_type_1(ws, Type::Window);
v.push(WsOrWin::Win { win }); if first {
wins.rotate_left(1);
first = false;
} }
v.append(&mut wins);
} }
v
}
}
pub struct Workspace<'a> { // Rotate until we have the second recently used workspace in front.
pub node: &'a s::Node, v.rotate_left(1);
extra_props: Option<ExtraProps>, while v[0].get_type() != Type::Workspace {
pub windows: Vec<Window<'a>>, v.rotate_left(1);
} }
impl Workspace<'_> { self.as_display_nodes(v)
pub fn get_name(&self) -> &str {
self.node.name.as_ref().unwrap()
} }
pub fn get_id(&self) -> i64 { pub fn is_child_of_tiled_container(&self, id: i64) -> bool {
self.node.id match self.get_parent_node(id) {
Some(n) => {
n.layout == s::NodeLayout::SplitH
|| n.layout == s::NodeLayout::SplitV
}
None => false,
}
} }
pub fn get_windows(&self) -> &Vec<Window> { pub fn is_child_of_tabbed_or_stacked_container(&self, id: i64) -> bool {
&self.windows match self.get_parent_node(id) {
Some(n) => {
n.layout == s::NodeLayout::Tabbed
|| n.layout == s::NodeLayout::Stacked
}
None => false,
}
} }
}
pub fn is_scratchpad(&self) -> bool { fn init_id_parent<'a>(
self.node.is_scratchpad() n: &'a s::Node,
} parent: Option<&'a s::Node>,
id_node: &mut HashMap<i64, &'a s::Node>,
id_parent: &mut HashMap<i64, i64>,
) {
id_node.insert(n.id, n);
pub fn is_focused(&self) -> bool { if let Some(p) = parent {
self.node.focused id_parent.insert(n.id, p.id);
} }
pub fn is_current(&self) -> bool { for c in &n.nodes {
is_current_container(self.node) init_id_parent(c, Some(n), id_node, id_parent);
}
for c in &n.floating_nodes {
init_id_parent(c, Some(n), id_node, id_parent);
} }
} }
pub fn is_current_container(node: &s::Node) -> bool { pub fn get_tree<'a>(
node.focused || NodeIter::new(node).any(|c| c.focused) root: &'a s::Node,
} extra_props: &'a HashMap<i64, ExtraProps>,
) -> Tree<'a> {
impl PartialEq for Workspace<'_> { let mut id_node: HashMap<i64, &s::Node> = HashMap::new();
fn eq(&self, other: &Self) -> bool { let mut id_parent: HashMap<i64, i64> = HashMap::new();
self.get_id() == other.get_id() init_id_parent(root, None, &mut id_node, &mut id_parent);
Tree {
root,
id_node,
id_parent,
extra_props,
} }
} }
impl Eq for Workspace<'_> {} lazy_static! {
static ref APP_NAME_AND_VERSION_RX: regex::Regex =
impl Ord for Workspace<'_> { regex::Regex::new("(.+)(-[0-9.]+)").unwrap();
fn cmp(&self, other: &Self) -> cmp::Ordering {
if self == other {
cmp::Ordering::Equal
} else {
let lru_a =
self.extra_props.as_ref().map_or(0, |wp| wp.last_focus_time);
let lru_b = other
.extra_props
.as_ref()
.map_or(0, |wp| wp.last_focus_time);
lru_a.cmp(&lru_b).reverse()
}
}
} }
impl PartialOrd for Workspace<'_> { fn maybe_html_escape(do_it: bool, text: &str) -> String {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { if do_it {
Some(self.cmp(other)) text.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("&", "&amp;")
} else {
text.to_string()
} }
} }
impl<'a> DisplayFormat for Workspace<'a> { impl DisplayFormat for DisplayNode<'_> {
fn format_for_display(&self, cfg: &cfg::Config) -> String { fn format_for_display(&self, cfg: &config::Config) -> String {
let fmt = cfg.get_format_workspace_format(); match self.node.get_type() {
let html_escape = cfg.get_format_html_escape(); Type::Root => String::from("Cannot format Root"),
Type::Output => String::from("Cannot format Output"),
fmt.replace("{id}", format!("{}", self.get_id()).as_str()) Type::Workspace => {
.replace("{name}", &maybe_html_escape(html_escape, self.get_name())) let fmt = cfg.get_format_workspace_format();
let html_escape = cfg.get_format_html_escape();
fmt.replace("{id}", format!("{}", self.node.id).as_str())
.replace(
"{name}",
&maybe_html_escape(html_escape, self.node.get_name()),
)
}
Type::Container => {
todo!("DisplayFormat for Container not yet implemented")
}
Type::Window => {
let window_format = cfg.get_format_window_format();
let urgency_start = cfg.get_format_urgency_start();
let urgency_end = cfg.get_format_urgency_end();
let html_escape = cfg.get_format_html_escape();
let icon_dirs = cfg.get_format_icon_dirs();
// fallback_icon has no default value.
let fallback_icon: Option<Box<std::path::Path>> =
cfg.get_format_fallback_icon().as_ref().map(|i| {
std::path::Path::new(i).to_owned().into_boxed_path()
});
// Some apps report, e.g., Gimp-2.10 but the icon is still named
// gimp.png.
let app_name_no_version = APP_NAME_AND_VERSION_RX
.replace(self.node.get_app_name(), "$1");
window_format
.replace("{id}", format!("{}", self.node.id).as_str())
.replace(
"{urgency_start}",
if self.node.urgent {
urgency_start.as_str()
} else {
""
},
)
.replace(
"{urgency_end}",
if self.node.urgent {
urgency_end.as_str()
} else {
""
},
)
.replace(
"{app_name}",
&maybe_html_escape(
html_escape,
self.node.get_app_name(),
),
)
.replace(
"{workspace_name}",
&maybe_html_escape(
html_escape,
self.tree
.get_workspace_node(self.node.id)
.map_or("<no_workspace>", |w| w.get_name()),
),
)
.replace(
"{marks}",
&maybe_html_escape(
html_escape,
&self.node.marks.join(", "),
),
)
.replace(
"{app_icon}",
util::get_icon(self.node.get_app_name(), &icon_dirs)
.or_else(|| {
util::get_icon(&app_name_no_version, &icon_dirs)
})
.or_else(|| {
util::get_icon(
&app_name_no_version.to_lowercase(),
&icon_dirs,
)
})
.or(fallback_icon)
.map(|i| i.to_string_lossy().into_owned())
.unwrap_or_else(String::new)
.as_str(),
)
.replace(
"{title}",
&maybe_html_escape(html_escape, self.node.get_name()),
)
}
}
} }
} }

@ -42,8 +42,8 @@ pub fn auto_tile(res_to_min_width: &HashMap<i32, i32>) {
if let Some(min_window_width) = min_window_width { if let Some(min_window_width) = min_window_width {
for container in con::NodeIter::new(output).filter(|n| { for container in con::NodeIter::new(output).filter(|n| {
n.node_type == s::NodeType::Workspace let t = n.get_type();
|| n.is_container() t == con::Type::Workspace || t == con::Type::Container
}) { }) {
if container.is_scratchpad() { if container.is_scratchpad() {
println!(" Skipping scratchpad"); println!(" Skipping scratchpad");
@ -55,8 +55,10 @@ pub fn auto_tile(res_to_min_width: &HashMap<i32, i32>) {
container.layout, container.layout,
container.nodes.len(), container.nodes.len(),
); );
for child_win in for child_win in container
container.nodes.iter().filter(|n| n.is_window()) .nodes
.iter()
.filter(|n| n.get_type() == con::Type::Window)
{ {
// Width if we'd split once more. // Width if we'd split once more.
let estimated_width = let estimated_width =
@ -132,17 +134,22 @@ const SWAYR_TMP_WORKSPACE: &str = "✨";
pub fn relayout_current_workspace( pub fn relayout_current_workspace(
include_floating: bool, include_floating: bool,
insert_win_fn: Box< insert_win_fn: Box<
dyn Fn(&mut [&con::Window], &mut s::Connection) -> s::Fallible<()>, dyn Fn(&mut [&s::Node], &mut s::Connection) -> s::Fallible<()>,
>, >,
) -> s::Fallible<()> { ) -> s::Fallible<()> {
let root = cmds::get_tree(); let root = cmds::get_tree(false);
let workspaces = con::get_workspaces(&root, false, &HashMap::new()); let workspaces: Vec<&s::Node> = root
.iter()
.filter(|n| n.get_type() == con::Type::Workspace)
.collect();
if let Some(cur_ws) = workspaces.iter().find(|ws| ws.is_current()) { if let Some(cur_ws) = workspaces.iter().find(|ws| ws.is_current()) {
if let Ok(mut con) = s::Connection::new() { if let Ok(mut con) = s::Connection::new() {
let mut moved_wins: Vec<&con::Window> = vec![]; let mut moved_wins: Vec<&s::Node> = vec![];
let mut focused_win = None; let mut focused_win = None;
for win in cur_ws.get_windows() { for win in
if win.is_focused() { cur_ws.iter().filter(|n| n.get_type() == con::Type::Window)
{
if win.focused {
focused_win = Some(win); focused_win = Some(win);
} }
if !include_floating && win.is_floating() { if !include_floating && win.is_floating() {
@ -151,8 +158,7 @@ pub fn relayout_current_workspace(
moved_wins.push(win); moved_wins.push(win);
con.run_command(format!( con.run_command(format!(
"[con_id={}] move to workspace {}", "[con_id={}] move to workspace {}",
win.get_id(), win.id, SWAYR_TMP_WORKSPACE
SWAYR_TMP_WORKSPACE
))?; ))?;
} }
@ -160,7 +166,7 @@ pub fn relayout_current_workspace(
std::thread::sleep(std::time::Duration::from_millis(25)); std::thread::sleep(std::time::Duration::from_millis(25));
if let Some(win) = focused_win { if let Some(win) = focused_win {
con.run_command(format!("[con_id={}] focus", win.get_id()))?; con.run_command(format!("[con_id={}] focus", win.id))?;
} }
Ok(()) Ok(())
} else { } else {

Loading…
Cancel
Save