You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
8.5 KiB
351 lines
8.5 KiB
use crate::ipc; |
|
use crate::util; |
|
use std::cmp; |
|
use std::collections::HashMap; |
|
use std::fmt; |
|
use std::os::unix::net::UnixStream; |
|
|
|
pub fn get_tree() -> ipc::Node { |
|
let output = util::swaymsg(&["-t", "get_tree"]); |
|
let json = output.as_str(); |
|
let result = serde_json::from_str(json); |
|
|
|
match result { |
|
Ok(node) => node, |
|
Err(err) => { |
|
panic!( |
|
"Can't read get_tree response: {}\nThe JSON is: {}", |
|
err, json |
|
); |
|
} |
|
} |
|
} |
|
|
|
#[test] |
|
fn test_get_tree() { |
|
let tree = get_tree(); |
|
|
|
println!("Those IDs are in get_tree():"); |
|
for n in tree.iter() { |
|
println!(" id: {}, type: {:?}", n.id, n.r#type); |
|
} |
|
} |
|
|
|
#[derive(Debug)] |
|
pub struct Window<'a> { |
|
node: &'a ipc::Node, |
|
workspace: &'a ipc::Node, |
|
con_props: Option<ipc::ConProps>, |
|
} |
|
|
|
impl Window<'_> { |
|
pub fn get_id(&self) -> &ipc::Id { |
|
&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() |
|
} |
|
} |
|
|
|
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.node.urgent && !other.node.urgent |
|
|| !self.node.focused && other.node.focused |
|
{ |
|
cmp::Ordering::Less |
|
} else if !self.node.urgent && other.node.urgent |
|
|| self.node.focused && !other.node.focused |
|
{ |
|
std::cmp::Ordering::Greater |
|
} else { |
|
let lru_a = |
|
self.con_props.as_ref().map_or(0, |wp| wp.last_focus_time); |
|
let lru_b = |
|
other.con_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)) |
|
} |
|
} |
|
|
|
impl<'a> fmt::Display for Window<'a> { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
|
write!( |
|
f, |
|
"<span font_weight=\"bold\" {}>“{}”</span> \ |
|
<i>{}</i> \ |
|
on workspace <b>{}</b> \ |
|
<span alpha=\"20000\">id {}</span>", // Almost hide ID! |
|
if self.node.urgent { |
|
" background=\"darkred\" foreground=\"white\"" |
|
} else { |
|
"" |
|
}, |
|
self.get_title(), |
|
self.get_app_name(), |
|
self.workspace.name.as_ref().unwrap(), |
|
self.get_id() |
|
) |
|
} |
|
} |
|
|
|
fn build_windows( |
|
root: &ipc::Node, |
|
mut con_props: HashMap<ipc::Id, ipc::ConProps>, |
|
) -> Vec<Window> { |
|
let mut v = vec![]; |
|
for workspace in root.workspaces() { |
|
for n in workspace.windows() { |
|
v.push(Window { |
|
node: &n, |
|
con_props: con_props.remove(&n.id), |
|
workspace: &workspace, |
|
}) |
|
} |
|
} |
|
v |
|
} |
|
|
|
fn build_workspaces( |
|
root: &ipc::Node, |
|
mut con_props: HashMap<ipc::Id, ipc::ConProps>, |
|
) -> Vec<Workspace> { |
|
let mut v = vec![]; |
|
for workspace in root.workspaces() { |
|
let mut wins: Vec<Window> = workspace |
|
.windows() |
|
.iter() |
|
.map(|w| Window { |
|
node: &w, |
|
con_props: con_props.remove(&w.id), |
|
workspace: &workspace, |
|
}) |
|
.collect(); |
|
wins.sort(); |
|
v.push(Workspace { |
|
node: &workspace, |
|
con_props: con_props.remove(&workspace.id), |
|
windows: wins, |
|
}) |
|
} |
|
v |
|
} |
|
|
|
fn get_con_props() -> Result<HashMap<ipc::Id, ipc::ConProps>, serde_json::Error> |
|
{ |
|
if let Ok(sock) = UnixStream::connect(util::get_swayr_socket_path()) { |
|
serde_json::from_reader(sock) |
|
} else { |
|
panic!("Could not connect to socket!") |
|
} |
|
} |
|
|
|
/// Gets all application windows of the tree. |
|
pub fn get_windows(root: &ipc::Node) -> Vec<Window> { |
|
let con_props = match get_con_props() { |
|
Ok(con_props) => Some(con_props), |
|
Err(e) => { |
|
eprintln!("Got no con_props: {:?}", e); |
|
None |
|
} |
|
}; |
|
|
|
build_windows(root, con_props.unwrap_or_default()) |
|
} |
|
|
|
/// Gets all application windows of the tree. |
|
pub fn get_workspaces( |
|
root: &ipc::Node, |
|
include_scratchpad: bool, |
|
) -> Vec<Workspace> { |
|
let con_props = match get_con_props() { |
|
Ok(con_props) => Some(con_props), |
|
Err(e) => { |
|
eprintln!("Got no con_props: {:?}", e); |
|
None |
|
} |
|
}; |
|
|
|
let workspaces = build_workspaces(root, con_props.unwrap_or_default()); |
|
let mut workspaces = if include_scratchpad { |
|
workspaces |
|
} else { |
|
workspaces |
|
.into_iter() |
|
.filter(|ws| !ws.is_scratchpad()) |
|
.collect() |
|
}; |
|
workspaces.sort(); |
|
println!( |
|
"Sorted WS: {:?}", |
|
workspaces |
|
.iter() |
|
.map(Workspace::get_name) |
|
.collect::<Vec<&str>>() |
|
); |
|
workspaces.rotate_left(1); |
|
println!( |
|
"Rotated WS: {:?}", |
|
workspaces |
|
.iter() |
|
.map(Workspace::get_name) |
|
.collect::<Vec<&str>>() |
|
); |
|
workspaces |
|
} |
|
|
|
#[test] |
|
fn test_get_windows() { |
|
let root = get_tree(); |
|
let cons = get_windows(&root); |
|
|
|
println!("There are {} cons.", cons.len()); |
|
|
|
for c in cons { |
|
println!(" {}", c); |
|
} |
|
} |
|
|
|
pub fn select_window<'a>( |
|
prompt: &'a str, |
|
windows: &'a [Window], |
|
) -> Option<&'a Window<'a>> { |
|
util::wofi_select(prompt, windows) |
|
} |
|
|
|
pub fn select_workspace<'a>( |
|
prompt: &'a str, |
|
workspaces: &'a [Workspace], |
|
) -> Option<&'a Workspace<'a>> { |
|
util::wofi_select(prompt, workspaces) |
|
} |
|
|
|
pub enum WsOrWin<'a> { |
|
Ws { ws: &'a Workspace<'a> }, |
|
Win { win: &'a Window<'a> }, |
|
} |
|
|
|
impl<'a> fmt::Display for WsOrWin<'a> { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
|
match self { |
|
WsOrWin::Ws { ws } => ws.fmt(f), |
|
WsOrWin::Win { win } => match f.write_str("\t") { |
|
Ok(()) => win.fmt(f), |
|
Err(e) => Err(e), |
|
}, |
|
} |
|
} |
|
} |
|
|
|
impl WsOrWin<'_> { |
|
pub fn from_workspaces<'a>( |
|
workspaces: &'a [Workspace], |
|
) -> Vec<WsOrWin<'a>> { |
|
let mut v = vec![]; |
|
for ws in workspaces { |
|
v.push(WsOrWin::Ws { ws }); |
|
for win in &ws.windows { |
|
v.push(WsOrWin::Win { win: &win }); |
|
} |
|
} |
|
v |
|
} |
|
} |
|
|
|
pub fn select_workspace_or_window<'a>( |
|
prompt: &'a str, |
|
ws_or_wins: &'a [WsOrWin<'a>], |
|
) -> Option<&'a WsOrWin<'a>> { |
|
util::wofi_select(prompt, ws_or_wins) |
|
} |
|
|
|
pub struct Workspace<'a> { |
|
node: &'a ipc::Node, |
|
con_props: Option<ipc::ConProps>, |
|
windows: Vec<Window<'a>>, |
|
} |
|
|
|
impl Workspace<'_> { |
|
pub fn get_name(&self) -> &str { |
|
self.node.name.as_ref().unwrap() |
|
} |
|
|
|
pub fn get_id(&self) -> &ipc::Id { |
|
&self.node.id |
|
} |
|
|
|
pub fn is_scratchpad(&self) -> bool { |
|
self.get_name().eq("__i3_scratch") |
|
} |
|
} |
|
|
|
impl PartialEq for Workspace<'_> { |
|
fn eq(&self, other: &Self) -> bool { |
|
self.get_id() == other.get_id() |
|
} |
|
} |
|
|
|
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.con_props.as_ref().map_or(0, |wp| wp.last_focus_time); |
|
let lru_b = |
|
other.con_props.as_ref().map_or(0, |wp| wp.last_focus_time); |
|
lru_a.cmp(&lru_b).reverse() |
|
} |
|
} |
|
} |
|
|
|
impl PartialOrd for Workspace<'_> { |
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { |
|
Some(self.cmp(other)) |
|
} |
|
} |
|
|
|
impl<'a> fmt::Display for Workspace<'a> { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
|
write!( |
|
f, |
|
"<span font_weight=\"bold\">“Workspace {}”</span> \ |
|
<span alpha=\"20000\">id {}</span>", // Almost hide ID! |
|
self.get_name(), |
|
self.get_id() |
|
) |
|
} |
|
}
|
|
|