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
}
@ -216,16 +216,18 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Forward,
windows,
&*props.read().unwrap(),
Box::new(|w: &con::Window| {
!w.is_floating() && w.is_child_of_tiled_container()
Box::new(|dn: &con::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(|w: &con::Window| {
!w.is_floating() && w.is_child_of_tiled_container()
Box::new(|dn: &con::DisplayNode| {
!dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id)
}),
),
SwayrCommand::NextTabbedOrStackedWindow { windows } => {
@ -233,9 +235,11 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Forward,
windows,
&*props.read().unwrap(),
Box::new(|w: &con::Window| {
!w.is_floating()
&& w.is_child_of_tabbed_or_stacked_container()
Box::new(|dn: &con::DisplayNode| {
!dn.node.is_floating()
&& 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,
windows,
&*props.read().unwrap(),
Box::new(|w: &con::Window| {
!w.is_floating()
&& w.is_child_of_tabbed_or_stacked_container()
Box::new(|dn: &con::DisplayNode| {
!dn.node.is_floating()
&& 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,
windows,
&*props.read().unwrap(),
Box::new(|w: &con::Window| w.is_floating()),
Box::new(|dn: &con::DisplayNode| dn.node.is_floating()),
)
}
SwayrCommand::PrevFloatingWindow { windows } => {
@ -263,7 +269,7 @@ pub fn exec_swayr_cmd(args: ExecSwayrCmdArgs) {
Direction::Backward,
windows,
&*props.read().unwrap(),
Box::new(|w: &con::Window| w.is_floating()),
Box::new(|dn: &con::DisplayNode| dn.node.is_floating()),
)
}
SwayrCommand::NextWindowOfSameLayout { windows } => {
@ -375,25 +381,39 @@ fn quit_window_by_id(id: i64) {
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() {
Ok(mut con) => con.get_tree().expect("Got no root node"),
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(
extra_props: &HashMap<i64, con::ExtraProps>,
) {
let root = get_tree();
let windows = con::get_windows(&root, false, extra_props);
if let Some(win) = windows
.iter()
.find(|w| w.is_urgent())
.or_else(|| windows.get(0))
{
println!("Switching to {}, id: {}", win.get_app_name(), win.get_id());
focus_window_by_id(win.get_id())
let root = get_tree(false);
let tree = con::get_tree(&root, extra_props);
if let Some(win) = tree.get_windows().get(0) {
println!(
"Switching to {}, id: {}",
win.node.get_app_name(),
win.node.id
);
focus_window_by_id(win.node.id)
} else {
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>) {
let root = get_tree();
let windows = con::get_windows(&root, true, extra_props);
let root = get_tree(true);
let tree = con::get_tree(&root, extra_props);
match util::select_from_menu("Switch to window", &windows) {
Ok(window) => focus_window_by_id(window.get_id()),
match util::select_from_menu("Switch to window", &tree.get_windows()) {
Ok(window) => focus_window_by_id(window.node.id),
Err(non_matching_input) => {
handle_non_matching_input(&non_matching_input)
}
@ -467,54 +487,49 @@ pub fn focus_window_in_direction(
dir: Direction,
consider_wins: &ConsiderWindows,
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 = match consider_wins {
ConsiderWindows::AllWorkspaces => &root,
ConsiderWindows::CurrentWorkspace => {
con::get_workspaces(&root, false, extra_props)
.iter()
.find(|w| w.is_current())
.expect("No current workspace!")
.node
}
};
let root = get_tree(false);
let tree = con::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_workspace_node(w.node.id).unwrap().id == cur_ws.id
});
}
let mut windows: Vec<con::Window> =
con::get_windows(root, false, extra_props)
.into_iter()
.filter(|w| pred(w))
.collect();
wins.retain(pred);
if windows.len() < 2 {
if wins.len() < 2 {
return;
}
windows.sort_by(|a, b| {
let lru_a = a.last_focus_time_for_next_prev_seq();
let lru_b = b.last_focus_time_for_next_prev_seq();
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<dyn Fn(&con::Window) -> bool> =
if !windows.iter().any(|w| w.is_focused()) {
let last_focused_win_id = windows.get(0).unwrap().get_id();
Box::new(move |w| w.get_id() == last_focused_win_id)
let is_focused_window: Box<dyn Fn(&con::DisplayNode) -> 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(|w: &con::Window| w.is_focused())
Box::new(|dn| dn.node.focused)
};
let mut iter: Box<dyn Iterator<Item = &con::Window>> = match dir {
Direction::Forward => Box::new(windows.iter().rev().cycle()),
Direction::Backward => Box::new(windows.iter().cycle()),
let mut iter: Box<dyn Iterator<Item = &con::DisplayNode>> = 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.get_id());
focus_window_by_id(win.node.id);
return;
}
}
@ -525,32 +540,35 @@ pub fn focus_window_of_same_layout_in_direction(
consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, con::ExtraProps>,
) {
let root = get_tree();
let windows = con::get_windows(&root, false, extra_props);
let current_window = windows
.iter()
.find(|w| w.is_focused())
.or_else(|| windows.get(0));
if let Some(current_window) = current_window {
let root = get_tree(false);
let tree = con::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 current_window.is_floating() {
Box::new(|w| w.is_floating())
} else if !current_window.is_floating()
&& current_window.is_child_of_tabbed_or_stacked_container()
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(|w| {
!w.is_floating()
&& w.is_child_of_tabbed_or_stacked_container()
Box::new(|dn| {
!dn.node.is_floating()
&& dn
.tree
.is_child_of_tabbed_or_stacked_container(dn.node.id)
})
} else if !current_window.is_floating()
&& current_window.is_child_of_tiled_container()
} else if !cur_win.node.is_floating()
&& cur_win.tree.is_child_of_tiled_container(cur_win.node.id)
{
Box::new(|w| {
!w.is_floating() && w.is_child_of_tiled_container()
Box::new(|dn| {
!dn.node.is_floating()
&& dn.tree.is_child_of_tiled_container(dn.node.id)
})
} else {
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>) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, false, extra_props);
let root = get_tree(false);
let tree = con::get_tree(&root, extra_props);
match util::select_from_menu("Switch to workspace", &workspaces) {
Ok(workspace) => run_sway_command(&["workspace", workspace.get_name()]),
match util::select_from_menu("Switch to workspace", &tree.get_workspaces())
{
Ok(workspace) => {
run_sway_command(&["workspace", workspace.node.get_name()])
}
Err(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(
extra_props: &HashMap<i64, con::ExtraProps>,
) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, false, extra_props);
let root = get_tree(true);
let tree = con::get_tree(&root, extra_props);
let workspaces = tree.get_workspaces();
let val = util::select_from_menu(
"Move focused container to workspace",
&workspaces,
);
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)),
};
@ -601,15 +623,20 @@ pub fn move_focused_container_to_workspace(
}
pub fn switch_workspace_or_window(extra_props: &HashMap<i64, con::ExtraProps>) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, true, extra_props);
let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces);
let root = get_tree(true);
let tree = con::get_tree(&root, extra_props);
let ws_or_wins = tree.get_workspaces_and_windows();
match util::select_from_menu("Select workspace or window", &ws_or_wins) {
Ok(ws_or_win) => match ws_or_win {
con::WsOrWin::Ws { ws } => {
run_sway_command(&["workspace", ws.get_name()]);
Ok(tn) => match tn.node.get_type() {
con::Type::Workspace => {
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) => {
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>) {
let root = get_tree();
let windows = con::get_windows(&root, true, extra_props);
let root = get_tree(true);
let tree = con::get_tree(&root, extra_props);
if let Ok(window) = util::select_from_menu("Quit window", &windows) {
quit_window_by_id(window.get_id())
if let Ok(window) =
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>) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, true, extra_props);
let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces);
if let Ok(ws_or_win) =
let root = get_tree(true);
let tree = con::get_tree(&root, extra_props);
let ws_or_wins = tree.get_workspaces_and_windows();
if let Ok(tn) =
util::select_from_menu("Quit workspace or window", &ws_or_wins)
{
match ws_or_win {
con::WsOrWin::Ws { ws } => {
for win in ws.get_windows() {
quit_window_by_id(win.get_id())
match tn.node.get_type() {
con::Type::Workspace => {
for win in
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() {
con.run_command(format!(
"[con_id={}] floating disable",
win.get_id()
win.id
))?;
}
std::thread::sleep(std::time::Duration::from_millis(25));
con.run_command(format!(
"[con_id={}] move to workspace current",
win.get_id()
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.get_id()
))?;
con.run_command(format!("[con_id={}] focus", win.id))?;
}
}
}
@ -702,14 +733,14 @@ fn tab_current_workspace(floating: &ConsiderFloating) {
if win.is_floating() {
con.run_command(format!(
"[con_id={}] floating disable",
win.get_id()
win.id
))?;
}
std::thread::sleep(std::time::Duration::from_millis(25));
con.run_command(format!(
"[con_id={}] move to workspace current",
win.get_id()
win.id
))?;
placed_wins.push(win);
}
@ -722,12 +753,9 @@ fn tab_current_workspace(floating: &ConsiderFloating) {
}
fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) {
let tree = get_tree();
let workspaces = tree.workspaces();
let cur_ws = workspaces
.iter()
.find(|w| con::is_current_container(w))
.unwrap();
let tree = get_tree(false);
let workspaces = tree.nodes_of_type(con::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 {

@ -15,7 +15,7 @@
//! Convenience data structures built from the IPC structs.
use crate::config as cfg;
use crate::config;
use crate::util;
use crate::util::DisplayFormat;
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`].
pub trait NodeMethods {
/// Returns an iterator for this [`swayipc::Node`] and its childres.
fn iter(&self) -> NodeIter;
fn is_window(&self) -> bool;
/// 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 true if this node is an output.
fn get_type(&self) -> Type;
/// Returns all nodes being application windows.
fn windows(&self) -> Vec<&s::Node>;
/// Returns the app_id if present, otherwise the window-properties class if
/// present, otherwise "<unknown_app>".
fn get_app_name(&self) -> &str;
/// Returns all nodes being containers.
fn containers(&self) -> Vec<&s::Node>;
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node>;
fn get_name(&self) -> &str;
/// Returns all nodes being workspaces.
fn workspaces(&self) -> Vec<&s::Node>;
// Returns true if this node is the scratchpad workspace.
// Returns true if this node is the scratchpad output or workspace.
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 {
@ -84,34 +93,66 @@ impl NodeMethods for s::Node {
NodeIter::new(self)
}
fn is_window(&self) -> bool {
(self.node_type == s::NodeType::Con
|| self.node_type == s::NodeType::FloatingCon)
&& self.name.is_some()
fn get_type(&self) -> Type {
match self.node_type {
s::NodeType::Root => Type::Root,
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 {
self.node_type == s::NodeType::Con
&& self.name.is_none()
&& self.layout != s::NodeLayout::None
fn get_name(&self) -> &str {
if let Some(name) = &self.name {
name.as_ref()
} else {
"<unnamed>"
}
}
fn windows(&self) -> Vec<&s::Node> {
self.iter().filter(|n| n.is_window()).collect()
fn get_app_name(&self) -> &str {
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> {
self.iter().filter(|n| n.is_container()).collect()
fn is_scratchpad(&self) -> bool {
let name = self.get_name();
name.eq("__i3") || name.eq("__i3_scratch")
}
fn workspaces(&self) -> Vec<&s::Node> {
self.iter()
.filter(|n| n.node_type == s::NodeType::Workspace)
.collect()
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
self.iter().filter(|n| n.get_type() == t).collect()
}
fn is_scratchpad(&self) -> bool {
self.name.is_some() && self.name.as_ref().unwrap().eq("__i3_scratch")
fn is_floating(&self) -> bool {
self.node_type == s::NodeType::FloatingCon
}
}
@ -123,386 +164,295 @@ pub struct ExtraProps {
pub last_focus_time_for_next_prev_seq: u128,
}
#[derive(Debug)]
pub struct Window<'a> {
node: &'a s::Node,
workspace: &'a s::Node,
extra_props: Option<ExtraProps>,
pub struct Tree<'a> {
root: &'a s::Node,
id_node: HashMap<i64, &'a s::Node>,
id_parent: HashMap<i64, i64>,
extra_props: &'a HashMap<i64, ExtraProps>,
}
impl Window<'_> {
pub fn get_id(&self) -> i64 {
self.node.id
}
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 struct DisplayNode<'a> {
pub node: &'a s::Node,
pub tree: &'a Tree<'a>,
}
pub fn is_floating(&self) -> bool {
self.node.node_type == s::NodeType::FloatingCon
impl<'a> Tree<'a> {
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 {
NodeIter::new(self.workspace)
.find(|n| {
n.nodes.contains(self.node)
|| n.floating_nodes.contains(self.node)
})
.unwrap_or_else(|| panic!("Window {:?} has no parent node!", self))
fn get_parent_node(&self, id: i64) -> Option<&&s::Node> {
self.id_parent.get(&id).map(|pid| self.get_node_by_id(*pid))
}
pub fn is_child_of_tiled_container(&self) -> bool {
let layout = &self.get_parent().layout;
layout == &s::NodeLayout::SplitH || layout == &s::NodeLayout::SplitV
pub fn get_workspace_node(&self, id: i64) -> Option<&&s::Node> {
let n = self.get_node_by_id(id);
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 {
let layout = &self.get_parent().layout;
layout == &s::NodeLayout::Tabbed || layout == &s::NodeLayout::Stacked
pub fn last_focus_time(&self, id: i64) -> u128 {
self.extra_props.get(&id).map_or(0, |wp| wp.last_focus_time)
}
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
.as_ref()
.get(&id)
.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<'_> {}
impl Ord for Window<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
if self == other {
cmp::Ordering::Equal
} else if self.is_urgent() && !other.is_urgent()
|| !self.is_focused() && other.is_focused()
{
cmp::Ordering::Less
} else if !self.is_urgent() && other.is_urgent()
|| self.is_focused() && !other.is_focused()
{
std::cmp::Ordering::Greater
} 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 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()
fn sorted_nodes_of_type_1(
&self,
node: &'a s::Node,
t: Type,
) -> Vec<&s::Node> {
let mut v: Vec<&s::Node> = node.nodes_of_type(t);
v.sort_by(|a, b| {
if a.urgent && !b.urgent {
cmp::Ordering::Less
} else if !a.urgent && b.urgent {
cmp::Ordering::Greater
} else {
let lru_a = self.last_focus_time(a.id);
let lru_b = self.last_focus_time(b.id);
lru_a.cmp(&lru_b).reverse()
}
});
v
}
}
impl<'a> DisplayFormat for Window<'a> {
fn format_for_display(&self, cfg: &cfg::Config) -> String {
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 sorted_nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
self.sorted_nodes_of_type_1(self.root, t)
}
}
fn build_windows<'a>(
root: &'a s::Node,
include_scratchpad_windows: bool,
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 {
fn as_display_nodes(&self, v: Vec<&'a s::Node>) -> Vec<DisplayNode> {
v.iter()
.map(|n| DisplayNode {
node: n,
extra_props: extra_props.get(&n.id).cloned(),
workspace,
tree: self,
})
}
.collect()
}
v
}
fn build_workspaces<'a>(
root: &'a s::Node,
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()
pub fn get_current_workspace(&self) -> &s::Node {
self.root
.iter()
.map(|w| Window {
node: w,
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();
.find(|n| n.get_type() == Type::Workspace && n.is_current())
.expect("No current Workspace")
}
v
}
/// Gets all application windows of the tree.
pub fn get_windows<'a>(
root: &'a s::Node,
include_scratchpad_windows: bool,
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();
pub fn get_workspaces(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Workspace);
v.rotate_left(1);
self.as_display_nodes(v)
}
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> {
Ws { ws: &'a Workspace<'a> },
Win { win: &'a Window<'a> },
}
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),
}
pub fn get_windows(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Window);
v.rotate_left(1);
self.as_display_nodes(v)
}
}
impl WsOrWin<'_> {
pub fn from_workspaces<'a>(
workspaces: &'a [Workspace],
) -> Vec<WsOrWin<'a>> {
pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(Type::Workspace);
let mut first = true;
let mut v = vec![];
for ws in workspaces {
v.push(WsOrWin::Ws { ws });
for win in &ws.windows {
v.push(WsOrWin::Win { win });
v.push(ws);
let mut wins = self.sorted_nodes_of_type_1(ws, Type::Window);
if first {
wins.rotate_left(1);
first = false;
}
v.append(&mut wins);
}
v
}
}
pub struct Workspace<'a> {
pub node: &'a s::Node,
extra_props: Option<ExtraProps>,
pub windows: Vec<Window<'a>>,
}
// Rotate until we have the second recently used workspace in front.
v.rotate_left(1);
while v[0].get_type() != Type::Workspace {
v.rotate_left(1);
}
impl Workspace<'_> {
pub fn get_name(&self) -> &str {
self.node.name.as_ref().unwrap()
self.as_display_nodes(v)
}
pub fn get_id(&self) -> i64 {
self.node.id
pub fn is_child_of_tiled_container(&self, id: i64) -> bool {
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> {
&self.windows
pub fn is_child_of_tabbed_or_stacked_container(&self, id: i64) -> bool {
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 {
self.node.is_scratchpad()
}
fn init_id_parent<'a>(
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 {
self.node.focused
if let Some(p) = parent {
id_parent.insert(n.id, p.id);
}
pub fn is_current(&self) -> bool {
is_current_container(self.node)
for c in &n.nodes {
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 {
node.focused || NodeIter::new(node).any(|c| c.focused)
}
impl PartialEq for Workspace<'_> {
fn eq(&self, other: &Self) -> bool {
self.get_id() == other.get_id()
pub fn get_tree<'a>(
root: &'a s::Node,
extra_props: &'a HashMap<i64, ExtraProps>,
) -> Tree<'a> {
let mut id_node: HashMap<i64, &s::Node> = HashMap::new();
let mut id_parent: HashMap<i64, i64> = HashMap::new();
init_id_parent(root, None, &mut id_node, &mut id_parent);
Tree {
root,
id_node,
id_parent,
extra_props,
}
}
impl Eq for Workspace<'_> {}
impl Ord for Workspace<'_> {
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()
}
}
lazy_static! {
static ref APP_NAME_AND_VERSION_RX: regex::Regex =
regex::Regex::new("(.+)(-[0-9.]+)").unwrap();
}
impl PartialOrd for Workspace<'_> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
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 Workspace<'a> {
fn format_for_display(&self, cfg: &cfg::Config) -> String {
let fmt = cfg.get_format_workspace_format();
let html_escape = cfg.get_format_html_escape();
fmt.replace("{id}", format!("{}", self.get_id()).as_str())
.replace("{name}", &maybe_html_escape(html_escape, self.get_name()))
impl DisplayFormat for DisplayNode<'_> {
fn format_for_display(&self, cfg: &config::Config) -> String {
match self.node.get_type() {
Type::Root => String::from("Cannot format Root"),
Type::Output => String::from("Cannot format Output"),
Type::Workspace => {
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 {
for container in con::NodeIter::new(output).filter(|n| {
n.node_type == s::NodeType::Workspace
|| n.is_container()
let t = n.get_type();
t == con::Type::Workspace || t == con::Type::Container
}) {
if container.is_scratchpad() {
println!(" Skipping scratchpad");
@ -55,8 +55,10 @@ pub fn auto_tile(res_to_min_width: &HashMap<i32, i32>) {
container.layout,
container.nodes.len(),
);
for child_win in
container.nodes.iter().filter(|n| n.is_window())
for child_win in container
.nodes
.iter()
.filter(|n| n.get_type() == con::Type::Window)
{
// Width if we'd split once more.
let estimated_width =
@ -132,17 +134,22 @@ const SWAYR_TMP_WORKSPACE: &str = "✨";
pub fn relayout_current_workspace(
include_floating: bool,
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<()> {
let root = cmds::get_tree();
let workspaces = con::get_workspaces(&root, false, &HashMap::new());
let root = cmds::get_tree(false);
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 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;
for win in cur_ws.get_windows() {
if win.is_focused() {
for win in
cur_ws.iter().filter(|n| n.get_type() == con::Type::Window)
{
if win.focused {
focused_win = Some(win);
}
if !include_floating && win.is_floating() {
@ -151,8 +158,7 @@ pub fn relayout_current_workspace(
moved_wins.push(win);
con.run_command(format!(
"[con_id={}] move to workspace {}",
win.get_id(),
SWAYR_TMP_WORKSPACE
win.id, SWAYR_TMP_WORKSPACE
))?;
}
@ -160,7 +166,7 @@ pub fn relayout_current_workspace(
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()))?;
con.run_command(format!("[con_id={}] focus", win.id))?;
}
Ok(())
} else {

Loading…
Cancel
Save