Split basic sway IPC into separate mod; use that from window bar module

main
Tassilo Horn 3 years ago
parent 59fa701ab5
commit 37c85880e7
  1. 1
      TODO
  2. 2
      src/bar/module/sysinfo.rs
  3. 25
      src/bar/module/window.rs
  4. 61
      src/cmds.rs
  5. 175
      src/ipc.rs
  6. 15
      src/layout.rs
  7. 1
      src/lib.rs
  8. 197
      src/tree.rs

@ -1 +0,0 @@
- Switch from lazy_static to once_cell once the latter is in stable rust.

@ -43,7 +43,7 @@ impl BarModuleFn for BarModuleSysInfo {
} }
fn build(&self) -> s::Block { fn build(&self) -> s::Block {
let x = String::from("{cpu_usage_avg}"); let x = self.system.borrow().load_average().one.to_string();
self.system.borrow_mut().refresh_specifics( self.system.borrow_mut().refresh_specifics(
sysinfo::RefreshKind::new().with_cpu().with_memory(), sysinfo::RefreshKind::new().with_cpu().with_memory(),
); );

@ -16,24 +16,18 @@
//! The window `swayrbar` module. //! The window `swayrbar` module.
use crate::bar::module::BarModuleFn; use crate::bar::module::BarModuleFn;
use crate::tree::NodeIter; use crate::ipc;
use std::cell::RefCell; use crate::ipc::NodeMethods;
use swaybar_types as s; use swaybar_types as s;
use swayipc as ipc;
pub struct BarModuleWindow { pub struct BarModuleWindow {
pub instance: String, pub instance: String,
connection: RefCell<swayipc::Connection>,
} }
impl BarModuleFn for BarModuleWindow { impl BarModuleFn for BarModuleWindow {
fn init() -> Box<dyn BarModuleFn> { fn init() -> Box<dyn BarModuleFn> {
Box::new(BarModuleWindow { Box::new(BarModuleWindow {
instance: "0".to_string(), instance: "0".to_string(),
connection: RefCell::new(
ipc::Connection::new()
.expect("Couldn't get a sway IPC connection"),
),
}) })
} }
@ -46,19 +40,14 @@ impl BarModuleFn for BarModuleWindow {
} }
fn build(&self) -> s::Block { fn build(&self) -> s::Block {
let x: String = match self.connection.borrow_mut().get_tree() { let root = ipc::get_root_node(false);
Ok(root) => { let focused_win = root.iter().find(|n| n.focused);
let o: Option<&ipc::Node> = let app_name = focused_win.map_or("", |w| w.get_app_name());
NodeIter::new(&root).find(|n| n.focused); let title = focused_win.map_or("", |w| w.get_name());
o.map(|w| w.name.clone().unwrap_or_default())
.unwrap_or_else(String::new)
}
Err(err) => format!("{}", err),
};
s::Block { s::Block {
name: Some(Self::name()), name: Some(Self::name()),
instance: Some(self.instance.clone()), instance: Some(self.instance.clone()),
full_text: x, full_text: title.to_string() + " — " + app_name,
align: Some(s::Align::Right), align: Some(s::Align::Right),
markup: Some(s::Markup::Pango), markup: Some(s::Markup::Pango),
short_text: None, short_text: None,

@ -16,9 +16,10 @@
//! Functions and data structures of the swayr client. //! Functions and data structures of the swayr client.
use crate::config as cfg; use crate::config as cfg;
use crate::ipc;
use crate::ipc::NodeMethods;
use crate::layout; use crate::layout;
use crate::tree as t; use crate::tree as t;
use crate::tree::NodeMethods;
use crate::util; use crate::util;
use crate::util::DisplayFormat; use crate::util::DisplayFormat;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -500,7 +501,7 @@ pub fn switch_to_app_or_urgent_or_lru_window(
name: Option<&str>, name: Option<&str>,
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
) { ) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
let wins = tree.get_windows(); let wins = tree.get_windows();
let app_win = let app_win =
@ -512,7 +513,7 @@ pub fn switch_to_mark_or_urgent_or_lru_window(
con_mark: Option<&str>, con_mark: Option<&str>,
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
) { ) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
let wins = tree.get_windows(); let wins = tree.get_windows();
let marked_win = con_mark.and_then(|mark| { let marked_win = con_mark.and_then(|mark| {
@ -584,17 +585,17 @@ fn handle_non_matching_input(input: &str) {
fn select_and_focus(prompt: &str, choices: &[t::DisplayNode]) { fn select_and_focus(prompt: &str, choices: &[t::DisplayNode]) {
match util::select_from_menu(prompt, choices) { match util::select_from_menu(prompt, choices) {
Ok(tn) => match tn.node.get_type() { Ok(tn) => match tn.node.get_type() {
t::Type::Output => { ipc::Type::Output => {
if !tn.node.is_scratchpad() { if !tn.node.is_scratchpad() {
run_sway_command(&["focus output", tn.node.get_name()]); run_sway_command(&["focus output", tn.node.get_name()]);
} }
} }
t::Type::Workspace => { ipc::Type::Workspace => {
if !tn.node.is_scratchpad() { if !tn.node.is_scratchpad() {
run_sway_command(&["workspace", tn.node.get_name()]); run_sway_command(&["workspace", tn.node.get_name()]);
} }
} }
t::Type::Window | t::Type::Container => { ipc::Type::Window | ipc::Type::Container => {
focus_window_by_id(tn.node.id) focus_window_by_id(tn.node.id)
} }
t => { t => {
@ -608,25 +609,25 @@ fn select_and_focus(prompt: &str, choices: &[t::DisplayNode]) {
} }
pub fn switch_window(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_window(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus("Select window", &tree.get_windows()); select_and_focus("Select window", &tree.get_windows());
} }
pub fn switch_workspace(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_workspace(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus("Select workspace", &tree.get_workspaces()); select_and_focus("Select workspace", &tree.get_workspaces());
} }
pub fn switch_output(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_output(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus("Select output", &tree.get_outputs()); select_and_focus("Select output", &tree.get_outputs());
} }
pub fn switch_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus( select_and_focus(
"Select workspace or window", "Select workspace or window",
@ -637,7 +638,7 @@ pub fn switch_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) {
pub fn switch_workspace_container_or_window( pub fn switch_workspace_container_or_window(
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
) { ) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus( select_and_focus(
"Select workspace, container or window", "Select workspace, container or window",
@ -646,7 +647,7 @@ pub fn switch_workspace_container_or_window(
} }
pub fn switch_to(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn switch_to(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_focus( select_and_focus(
"Select output, workspace, container or window", "Select output, workspace, container or window",
@ -671,14 +672,14 @@ fn kill_process_by_pid(pid: Option<i32>) {
fn select_and_quit(prompt: &str, choices: &[t::DisplayNode], kill: bool) { fn select_and_quit(prompt: &str, choices: &[t::DisplayNode], kill: bool) {
if let Ok(tn) = util::select_from_menu(prompt, choices) { if let Ok(tn) = util::select_from_menu(prompt, choices) {
match tn.node.get_type() { match tn.node.get_type() {
t::Type::Workspace | t::Type::Container => { ipc::Type::Workspace | ipc::Type::Container => {
for win in for win in
tn.node.iter().filter(|n| n.get_type() == t::Type::Window) tn.node.iter().filter(|n| n.get_type() == ipc::Type::Window)
{ {
quit_window_by_id(win.id) quit_window_by_id(win.id)
} }
} }
t::Type::Window => { ipc::Type::Window => {
if kill { if kill {
kill_process_by_pid(tn.node.pid) kill_process_by_pid(tn.node.pid)
} else { } else {
@ -693,13 +694,13 @@ fn select_and_quit(prompt: &str, choices: &[t::DisplayNode], kill: bool) {
} }
pub fn quit_window(extra_props: &HashMap<i64, t::ExtraProps>, kill: bool) { pub fn quit_window(extra_props: &HashMap<i64, t::ExtraProps>, kill: bool) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_quit("Quit window", &tree.get_windows(), kill); select_and_quit("Quit window", &tree.get_windows(), kill);
} }
pub fn quit_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn quit_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_quit( select_and_quit(
"Quit workspace or window", "Quit workspace or window",
@ -711,7 +712,7 @@ pub fn quit_workspace_or_window(extra_props: &HashMap<i64, t::ExtraProps>) {
pub fn quit_workspace_container_or_window( pub fn quit_workspace_container_or_window(
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
) { ) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_quit( select_and_quit(
"Quit workspace, container or window", "Quit workspace, container or window",
@ -749,7 +750,7 @@ fn move_focused_to_container_or_window(id: i64) {
fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) { fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) {
match util::select_from_menu(prompt, choices) { match util::select_from_menu(prompt, choices) {
Ok(tn) => match tn.node.get_type() { Ok(tn) => match tn.node.get_type() {
t::Type::Output => { ipc::Type::Output => {
if tn.node.is_scratchpad() { if tn.node.is_scratchpad() {
run_sway_command_1("move container to scratchpad") run_sway_command_1("move container to scratchpad")
} else { } else {
@ -759,14 +760,14 @@ fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) {
]) ])
} }
} }
t::Type::Workspace => { ipc::Type::Workspace => {
if tn.node.is_scratchpad() { if tn.node.is_scratchpad() {
run_sway_command_1("move container to scratchpad") run_sway_command_1("move container to scratchpad")
} else { } else {
move_focused_to_workspace_1(tn.node.get_name()) move_focused_to_workspace_1(tn.node.get_name())
} }
} }
t::Type::Container | t::Type::Window => { ipc::Type::Container | ipc::Type::Window => {
move_focused_to_container_or_window(tn.node.id) move_focused_to_container_or_window(tn.node.id)
} }
t => log::error!("Cannot move focused to {:?}", t), t => log::error!("Cannot move focused to {:?}", t),
@ -779,7 +780,7 @@ fn select_and_move_focused_to(prompt: &str, choices: &[t::DisplayNode]) {
} }
pub fn move_focused_to_workspace(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn move_focused_to_workspace(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_move_focused_to( select_and_move_focused_to(
"Move focused container to workspace", "Move focused container to workspace",
@ -788,7 +789,7 @@ pub fn move_focused_to_workspace(extra_props: &HashMap<i64, t::ExtraProps>) {
} }
pub fn move_focused_to(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn move_focused_to(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
select_and_move_focused_to( select_and_move_focused_to(
"Move focused container to workspace or container", "Move focused container to workspace or container",
@ -797,14 +798,14 @@ pub fn move_focused_to(extra_props: &HashMap<i64, t::ExtraProps>) {
} }
pub fn swap_focused_with(extra_props: &HashMap<i64, t::ExtraProps>) { pub fn swap_focused_with(extra_props: &HashMap<i64, t::ExtraProps>) {
let root = t::get_root_node(true); let root = ipc::get_root_node(true);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
match util::select_from_menu( match util::select_from_menu(
"Swap focused with", "Swap focused with",
&tree.get_workspaces_containers_and_windows(), &tree.get_workspaces_containers_and_windows(),
) { ) {
Ok(tn) => match tn.node.get_type() { Ok(tn) => match tn.node.get_type() {
t::Type::Workspace | t::Type::Container | t::Type::Window => { ipc::Type::Workspace | ipc::Type::Container | ipc::Type::Window => {
run_sway_command(&[ run_sway_command(&[
"swap", "swap",
"container", "container",
@ -833,14 +834,14 @@ pub fn focus_window_in_direction(
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
pred: Box<dyn Fn(&t::DisplayNode) -> bool>, pred: Box<dyn Fn(&t::DisplayNode) -> bool>,
) { ) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
let mut wins = tree.get_windows(); let mut wins = tree.get_windows();
if consider_wins == &ConsiderWindows::CurrentWorkspace { if consider_wins == &ConsiderWindows::CurrentWorkspace {
let cur_ws = tree.get_current_workspace(); let cur_ws = tree.get_current_workspace();
wins.retain(|w| { wins.retain(|w| {
tree.get_parent_node_of_type(w.node.id, t::Type::Workspace) tree.get_parent_node_of_type(w.node.id, ipc::Type::Workspace)
.unwrap() .unwrap()
.id .id
== cur_ws.id == cur_ws.id
@ -887,7 +888,7 @@ pub fn focus_window_of_same_layout_in_direction(
consider_wins: &ConsiderWindows, consider_wins: &ConsiderWindows,
extra_props: &HashMap<i64, t::ExtraProps>, extra_props: &HashMap<i64, t::ExtraProps>,
) { ) {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let tree = t::get_tree(&root, extra_props); let tree = t::get_tree(&root, extra_props);
let wins = tree.get_windows(); let wins = tree.get_windows();
let cur_win = wins.iter().find(|w| w.node.focused); let cur_win = wins.iter().find(|w| w.node.focused);
@ -999,8 +1000,8 @@ fn tab_current_workspace(floating: &ConsiderFloating) {
} }
fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) { fn toggle_tab_tile_current_workspace(floating: &ConsiderFloating) {
let tree = t::get_root_node(false); let tree = ipc::get_root_node(false);
let workspaces = tree.nodes_of_type(t::Type::Workspace); let workspaces = tree.nodes_of_type(ipc::Type::Workspace);
let cur_ws = workspaces.iter().find(|w| w.is_current()).unwrap(); let cur_ws = workspaces.iter().find(|w| w.is_current()).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);

@ -0,0 +1,175 @@
// Copyright (C) 2021-2022 Tassilo Horn <tsdh@gnu.org>
//
// 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 <https://www.gnu.org/licenses/>.
//! Basic sway IPC.
use std::{cell::RefCell, sync::Mutex};
use once_cell::sync::Lazy;
use swayipc as s;
static SWAY_IPC_CONNECTION: Lazy<Mutex<RefCell<s::Connection>>> =
Lazy::new(|| {
Mutex::new(RefCell::new(
s::Connection::new().expect("Could not open sway IPC connection."),
))
});
pub fn get_root_node(include_scratchpad: bool) -> s::Node {
let mut root = match SWAY_IPC_CONNECTION.lock() {
Ok(cell) => cell.borrow_mut().get_tree().expect("Couldn't get tree"),
Err(err) => panic!("{}", err),
};
if !include_scratchpad {
root.nodes.retain(|o| !o.is_scratchpad());
}
root
}
/// Immutable Node Iterator
///
/// Iterates nodes in depth-first order, tiled nodes before floating nodes.
pub struct NodeIter<'a> {
stack: Vec<&'a s::Node>,
}
impl<'a> NodeIter<'a> {
pub fn new(node: &'a s::Node) -> NodeIter {
NodeIter { stack: vec![node] }
}
}
impl<'a> Iterator for NodeIter<'a> {
type Item = &'a s::Node;
fn next(&mut self) -> Option<Self::Item> {
if let Some(node) = self.stack.pop() {
for n in &node.floating_nodes {
self.stack.push(n);
}
for n in &node.nodes {
self.stack.push(n);
}
Some(node)
} else {
None
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Type {
Root,
Output,
Workspace,
Container,
Window,
}
/// Extension methods for [`swayipc::Node`].
pub trait NodeMethods {
fn iter(&self) -> NodeIter;
fn get_type(&self) -> Type;
fn get_app_name(&self) -> &str;
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node>;
fn get_name(&self) -> &str;
fn is_scratchpad(&self) -> bool;
fn is_floating(&self) -> bool;
fn is_current(&self) -> bool;
}
impl NodeMethods for s::Node {
fn iter(&self) -> NodeIter {
NodeIter::new(self)
}
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.app_id.is_none()
&& self.pid.is_none()
&& self.shell.is_none()
&& self.window_properties.is_none()
&& self.layout != s::NodeLayout::None
{
Type::Container
} else if (self.node_type == s::NodeType::Con
|| self.node_type == s::NodeType::FloatingCon)
// Apparently there can be windows without app_id, name,
// and window_properties.class, e.g., dolphin-emu-nogui.
&& self.pid.is_some()
// FIXME: While technically correct, old sway versions (up to
// at least sway-1.4) don't expose shell in IPC. So comment in
// again when all major distros have a recent enough sway
// package.
//&& self.shell.is_some()
{
Type::Window
} else {
panic!(
"Don't know type of node with id {} and node_type {:?}\n{:?}",
self.id, self.node_type, self
)
}
}
}
}
fn get_name(&self) -> &str {
if let Some(name) = &self.name {
name.as_ref()
} else {
"<unnamed>"
}
}
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 is_scratchpad(&self) -> bool {
let name = self.get_name();
name.eq("__i3") || name.eq("__i3_scratch")
}
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
self.iter().filter(|n| n.get_type() == t).collect()
}
fn is_floating(&self) -> bool {
self.node_type == s::NodeType::FloatingCon
}
fn is_current(&self) -> bool {
self.iter().any(|n| n.focused)
}
}

@ -16,8 +16,8 @@
//! Functions and data structures of the swayrd demon. //! Functions and data structures of the swayrd demon.
use crate::config; use crate::config;
use crate::tree as t; use crate::ipc;
use crate::tree::NodeMethods; use crate::ipc::NodeMethods;
use std::collections::HashMap; use std::collections::HashMap;
use swayipc as s; use swayipc as s;
@ -42,7 +42,7 @@ 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 output.iter().filter(|n| { for container in output.iter().filter(|n| {
let t = n.get_type(); let t = n.get_type();
t == t::Type::Workspace || t == t::Type::Container t == ipc::Type::Workspace || t == ipc::Type::Container
}) { }) {
if container.is_scratchpad() { if container.is_scratchpad() {
log::debug!(" Skipping scratchpad"); log::debug!(" Skipping scratchpad");
@ -57,7 +57,7 @@ pub fn auto_tile(res_to_min_width: &HashMap<i32, i32>) {
for child_win in container for child_win in container
.nodes .nodes
.iter() .iter()
.filter(|n| n.get_type() == t::Type::Window) .filter(|n| n.get_type() == ipc::Type::Window)
{ {
// Width if we'd split once more. // Width if we'd split once more.
let estimated_width = let estimated_width =
@ -138,16 +138,17 @@ pub fn relayout_current_workspace(
dyn Fn(&mut [&s::Node], &mut s::Connection) -> s::Fallible<()>, dyn Fn(&mut [&s::Node], &mut s::Connection) -> s::Fallible<()>,
>, >,
) -> s::Fallible<()> { ) -> s::Fallible<()> {
let root = t::get_root_node(false); let root = ipc::get_root_node(false);
let workspaces: Vec<&s::Node> = root let workspaces: Vec<&s::Node> = root
.iter() .iter()
.filter(|n| n.get_type() == t::Type::Workspace) .filter(|n| n.get_type() == ipc::Type::Workspace)
.collect(); .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<&s::Node> = vec![]; let mut moved_wins: Vec<&s::Node> = vec![];
let mut focused_win = None; let mut focused_win = None;
for win in cur_ws.iter().filter(|n| n.get_type() == t::Type::Window) for win in
cur_ws.iter().filter(|n| n.get_type() == ipc::Type::Window)
{ {
if win.focused { if win.focused {
focused_win = Some(win); focused_win = Some(win);

@ -30,6 +30,7 @@ pub mod config;
pub mod demon; pub mod demon;
#[macro_use] #[macro_use]
pub mod fmt_replace; pub mod fmt_replace;
pub mod ipc;
pub mod layout; pub mod layout;
pub mod rtfmt; pub mod rtfmt;
pub mod tree; pub mod tree;

@ -16,6 +16,8 @@
//! Convenience data structures built from the IPC structs. //! Convenience data structures built from the IPC structs.
use crate::config; use crate::config;
use crate::ipc;
use crate::ipc::NodeMethods;
use crate::util; use crate::util;
use crate::util::DisplayFormat; use crate::util::DisplayFormat;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -25,163 +27,8 @@ use std::cell::RefCell;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Mutex;
use swayipc as s; use swayipc as s;
static SWAY_IPC_CONNECTION: Lazy<Mutex<RefCell<s::Connection>>> =
Lazy::new(|| {
Mutex::new(RefCell::new(
s::Connection::new().expect("Could not open sway IPC connection."),
))
});
pub fn get_root_node(include_scratchpad: bool) -> s::Node {
let mut root = match SWAY_IPC_CONNECTION.lock() {
Ok(cell) => cell.borrow_mut().get_tree().expect("Couldn't get tree"),
Err(err) => panic!("{}", err),
};
if !include_scratchpad {
root.nodes.retain(|o| !o.is_scratchpad());
}
root
}
/// Immutable Node Iterator
///
/// Iterates nodes in depth-first order, tiled nodes before floating nodes.
pub struct NodeIter<'a> {
stack: Vec<&'a s::Node>,
}
impl<'a> NodeIter<'a> {
pub fn new(node: &'a s::Node) -> NodeIter {
NodeIter { stack: vec![node] }
}
}
impl<'a> Iterator for NodeIter<'a> {
type Item = &'a s::Node;
fn next(&mut self) -> Option<Self::Item> {
if let Some(node) = self.stack.pop() {
for n in &node.floating_nodes {
self.stack.push(n);
}
for n in &node.nodes {
self.stack.push(n);
}
Some(node)
} else {
None
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Type {
Root,
Output,
Workspace,
Container,
Window,
}
/// Extension methods for [`swayipc::Node`].
pub trait NodeMethods {
fn iter(&self) -> NodeIter;
fn get_type(&self) -> Type;
fn get_app_name(&self) -> &str;
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node>;
fn get_name(&self) -> &str;
fn is_scratchpad(&self) -> bool;
fn is_floating(&self) -> bool;
fn is_current(&self) -> bool;
}
impl NodeMethods for s::Node {
fn iter(&self) -> NodeIter {
NodeIter::new(self)
}
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.app_id.is_none()
&& self.pid.is_none()
&& self.shell.is_none()
&& self.window_properties.is_none()
&& self.layout != s::NodeLayout::None
{
Type::Container
} else if (self.node_type == s::NodeType::Con
|| self.node_type == s::NodeType::FloatingCon)
// Apparently there can be windows without app_id, name,
// and window_properties.class, e.g., dolphin-emu-nogui.
&& self.pid.is_some()
// FIXME: While technically correct, old sway versions (up to
// at least sway-1.4) don't expose shell in IPC. So comment in
// again when all major distros have a recent enough sway
// package.
//&& self.shell.is_some()
{
Type::Window
} else {
panic!(
"Don't know type of node with id {} and node_type {:?}\n{:?}",
self.id, self.node_type, self
)
}
}
}
}
fn get_name(&self) -> &str {
if let Some(name) = &self.name {
name.as_ref()
} else {
"<unnamed>"
}
}
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 is_scratchpad(&self) -> bool {
let name = self.get_name();
name.eq("__i3") || name.eq("__i3_scratch")
}
fn nodes_of_type(&self, t: Type) -> Vec<&s::Node> {
self.iter().filter(|n| n.get_type() == t).collect()
}
fn is_floating(&self) -> bool {
self.node_type == s::NodeType::FloatingCon
}
fn is_current(&self) -> bool {
self.iter().any(|n| n.focused)
}
}
/// Extra properties gathered by swayrd for windows and workspaces. /// Extra properties gathered by swayrd for windows and workspaces.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct ExtraProps { pub struct ExtraProps {
@ -223,7 +70,7 @@ impl<'a> Tree<'a> {
pub fn get_parent_node_of_type( pub fn get_parent_node_of_type(
&self, &self,
id: i64, id: i64,
t: Type, t: ipc::Type,
) -> Option<&&s::Node> { ) -> Option<&&s::Node> {
let n = self.get_node_by_id(id); let n = self.get_node_by_id(id);
if n.get_type() == t { if n.get_type() == t {
@ -248,14 +95,14 @@ impl<'a> Tree<'a> {
fn sorted_nodes_of_type_1( fn sorted_nodes_of_type_1(
&self, &self,
node: &'a s::Node, node: &'a s::Node,
t: Type, t: ipc::Type,
) -> Vec<&s::Node> { ) -> Vec<&s::Node> {
let mut v: Vec<&s::Node> = node.nodes_of_type(t); let mut v: Vec<&s::Node> = node.nodes_of_type(t);
self.sort_by_urgency_and_lru_time_1(&mut v); self.sort_by_urgency_and_lru_time_1(&mut v);
v v
} }
fn sorted_nodes_of_type(&self, t: Type) -> Vec<&s::Node> { fn sorted_nodes_of_type(&self, t: ipc::Type) -> Vec<&s::Node> {
self.sorted_nodes_of_type_1(self.root, t) self.sorted_nodes_of_type_1(self.root, t)
} }
@ -276,7 +123,7 @@ impl<'a> Tree<'a> {
pub fn get_current_workspace(&self) -> &s::Node { pub fn get_current_workspace(&self) -> &s::Node {
self.root self.root
.iter() .iter()
.find(|n| n.get_type() == Type::Workspace && n.is_current()) .find(|n| n.get_type() == ipc::Type::Workspace && n.is_current())
.expect("No current Workspace") .expect("No current Workspace")
} }
@ -284,13 +131,13 @@ impl<'a> Tree<'a> {
let outputs: Vec<&s::Node> = self let outputs: Vec<&s::Node> = self
.root .root
.iter() .iter()
.filter(|n| n.get_type() == Type::Output && !n.is_scratchpad()) .filter(|n| n.get_type() == ipc::Type::Output && !n.is_scratchpad())
.collect(); .collect();
self.as_display_nodes(&outputs, IndentLevel::Fixed(0)) self.as_display_nodes(&outputs, IndentLevel::Fixed(0))
} }
pub fn get_workspaces(&self) -> Vec<DisplayNode> { pub fn get_workspaces(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Workspace); let mut v = self.sorted_nodes_of_type(ipc::Type::Workspace);
if !v.is_empty() { if !v.is_empty() {
v.rotate_left(1); v.rotate_left(1);
} }
@ -298,7 +145,7 @@ impl<'a> Tree<'a> {
} }
pub fn get_windows(&self) -> Vec<DisplayNode> { pub fn get_windows(&self) -> Vec<DisplayNode> {
let mut v = self.sorted_nodes_of_type(Type::Window); let mut v = self.sorted_nodes_of_type(ipc::Type::Window);
// Rotate, but only non-urgent windows. Those should stay at the front // Rotate, but only non-urgent windows. Those should stay at the front
// as they are the most likely switch candidates. // as they are the most likely switch candidates.
let mut x; let mut x;
@ -322,12 +169,12 @@ impl<'a> Tree<'a> {
} }
pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> { pub fn get_workspaces_and_windows(&self) -> Vec<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(Type::Workspace); let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace);
let mut first = true; let mut first = true;
let mut v = vec![]; let mut v = vec![];
for ws in workspaces { for ws in workspaces {
v.push(ws); v.push(ws);
let mut wins = self.sorted_nodes_of_type_1(ws, Type::Window); let mut wins = self.sorted_nodes_of_type_1(ws, ipc::Type::Window);
if first && !wins.is_empty() { if first && !wins.is_empty() {
wins.rotate_left(1); wins.rotate_left(1);
first = false; first = false;
@ -371,7 +218,7 @@ impl<'a> Tree<'a> {
pub fn get_outputs_workspaces_containers_and_windows( pub fn get_outputs_workspaces_containers_and_windows(
&self, &self,
) -> Vec<DisplayNode> { ) -> Vec<DisplayNode> {
let outputs = self.sorted_nodes_of_type(Type::Output); let outputs = self.sorted_nodes_of_type(ipc::Type::Output);
let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![])); let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![]));
for o in outputs { for o in outputs {
self.push_subtree_sorted(o, Rc::clone(&v)); self.push_subtree_sorted(o, Rc::clone(&v));
@ -382,7 +229,7 @@ impl<'a> Tree<'a> {
} }
pub fn get_workspaces_containers_and_windows(&self) -> Vec<DisplayNode> { pub fn get_workspaces_containers_and_windows(&self) -> Vec<DisplayNode> {
let workspaces = self.sorted_nodes_of_type(Type::Workspace); let workspaces = self.sorted_nodes_of_type(ipc::Type::Workspace);
let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![])); let v: Rc<RefCell<Vec<&s::Node>>> = Rc::new(RefCell::new(vec![]));
for ws in workspaces { for ws in workspaces {
self.push_subtree_sorted(ws, Rc::clone(&v)); self.push_subtree_sorted(ws, Rc::clone(&v));
@ -477,11 +324,11 @@ impl DisplayFormat for DisplayNode<'_> {
APP_NAME_AND_VERSION_RX.replace(self.node.get_app_name(), "$1"); APP_NAME_AND_VERSION_RX.replace(self.node.get_app_name(), "$1");
let fmt = match self.node.get_type() { let fmt = match self.node.get_type() {
Type::Root => String::from("Cannot format Root"), ipc::Type::Root => String::from("Cannot format Root"),
Type::Output => cfg.get_format_output_format(), ipc::Type::Output => cfg.get_format_output_format(),
Type::Workspace => cfg.get_format_workspace_format(), ipc::Type::Workspace => cfg.get_format_workspace_format(),
Type::Container => cfg.get_format_container_format(), ipc::Type::Container => cfg.get_format_container_format(),
Type::Window => cfg.get_format_window_format(), ipc::Type::Window => cfg.get_format_window_format(),
}; };
let fmt = fmt let fmt = fmt
.replace( .replace(
@ -529,11 +376,11 @@ impl DisplayFormat for DisplayNode<'_> {
"name" | "title" => self.node.get_name(), "name" | "title" => self.node.get_name(),
"output_name" => self "output_name" => self
.tree .tree
.get_parent_node_of_type(self.node.id, Type::Output) .get_parent_node_of_type(self.node.id, ipc::Type::Output)
.map_or("<no_output>", |w| w.get_name()), .map_or("<no_output>", |w| w.get_name()),
"workspace_name" => self "workspace_name" => self
.tree .tree
.get_parent_node_of_type(self.node.id, Type::Workspace) .get_parent_node_of_type(self.node.id, ipc::Type::Workspace)
.map_or("<no_workspace>", |w| w.get_name()), .map_or("<no_workspace>", |w| w.get_name()),
"marks" => format_marks(&self.node.marks), "marks" => format_marks(&self.node.marks),
}) })
@ -544,8 +391,8 @@ impl DisplayFormat for DisplayNode<'_> {
IndentLevel::Fixed(level) => level as usize, IndentLevel::Fixed(level) => level as usize,
IndentLevel::WorkspacesZeroWindowsOne => { IndentLevel::WorkspacesZeroWindowsOne => {
match self.node.get_type(){ match self.node.get_type(){
Type::Workspace => 0, ipc::Type::Workspace => 0,
Type::Window => 1, ipc::Type::Window => 1,
_ => panic!("Only Workspaces and Windows expected. File a bug report!") _ => panic!("Only Workspaces and Windows expected. File a bug report!")
} }
} }

Loading…
Cancel
Save