Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sway support as separate feature #138

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ codegen-units = 1

[features]
i3 = ["i3ipc"]
sway = ["swayipc"]

[dependencies]
cairo-sys-rs = "0.15"
css-color-parser = "0.1"
font-loader = "0.11"
i3ipc = { version = "0.10", optional = true }
swayipc = { version = "3.0.0", optional = true }
itertools = "0.10"
log = "0.4"
pretty_env_logger = "0.4"
Expand Down
28 changes: 23 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use xkbcommon::xkb;
mod args;
mod utils;

// begin i3-specific

#[cfg(feature = "i3")]
extern crate i3ipc;

Expand All @@ -17,10 +19,25 @@ mod wm_i3;
#[cfg(feature = "i3")]
use crate::wm_i3 as wm;

// end i3-specific

// begin sway-specific

#[cfg(feature = "sway")]
extern crate swayipc;

#[cfg(feature = "sway")]
mod wm_sway;

#[cfg(feature = "sway")]
use crate::wm_sway as wm;

// end sway-specific

#[derive(Debug)]
pub struct DesktopWindow {
id: i64,
x_window_id: Option<i32>,
x_window_id: Option<i64>,
pos: (i32, i32),
size: (i32, i32),
is_focused: bool,
Expand All @@ -34,7 +51,7 @@ pub struct RenderWindow<'a> {
rect: (i32, i32, i32, i32),
}

#[cfg(any(feature = "i3", feature = "add_some_other_wm_here"))]
#[cfg(any(feature = "i3", feature = "sway"))]
fn main() -> Result<()> {
pretty_env_logger::init();
let app_config = args::parse_args();
Expand Down Expand Up @@ -325,12 +342,13 @@ fn main() -> Result<()> {
Ok(())
}

#[cfg(not(any(feature = "i3", feature = "add_some_other_wm_here")))]
#[cfg(not(any(feature = "i3", feature = "sway")))]
fn main() -> Result<()> {
eprintln!(
"You need to enable support for at least one window manager.\n
"You need to enable support for one window manager.\n
Currently supported:
--features i3"
--features i3
--features sway"
);

Ok(())
Expand Down
120 changes: 120 additions & 0 deletions src/wm_sway.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use anyhow::{Context, Result};
use log::{debug, info};
use swayipc::{Connection, Node, NodeLayout, NodeType, Workspace};

use crate::DesktopWindow;

/// Find first `Node` that fulfills a given criterion.
fn find_first_node_with_attr<F>(start_node: &Node, predicate: F) -> Option<&Node>
where
F: Fn(&Node) -> bool,
{
let mut nodes_to_explore: Vec<&Node> = start_node.nodes.iter().collect();
while !nodes_to_explore.is_empty() {
let mut next_vec = vec![];
for node in &nodes_to_explore {
if predicate(node) {
return Some(node);
}
next_vec.extend(node.nodes.iter());
}
nodes_to_explore = next_vec;
}
None
}

/// Find parent of `child`.
fn find_parent_of<'a>(start_node: &'a Node, child: &'a Node) -> Option<&'a Node> {
let mut nodes_to_explore: Vec<&Node> = start_node.nodes.iter().collect();
while !nodes_to_explore.is_empty() {
let mut next_vec = vec![];
for node in &nodes_to_explore {
if node.nodes.iter().any(|x| child.id == x.id) {
return Some(node);
}
next_vec.extend(node.nodes.iter());
}
nodes_to_explore = next_vec;
}
None
}

/// Return a list of all `DesktopWindow`s for the given `Workspace`.
fn crawl_windows(root_node: &Node, workspace: &Workspace) -> Result<Vec<DesktopWindow>> {
let workspace_node = find_first_node_with_attr(root_node, |x| {
x.name == Some(workspace.name.clone()) && x.node_type == NodeType::Workspace
})
.context("Couldn't find the Workspace node")?;

let mut nodes_to_explore: Vec<&Node> = workspace_node.nodes.iter().collect();
nodes_to_explore.extend(workspace_node.floating_nodes.iter());
let mut windows = vec![];
while !nodes_to_explore.is_empty() {
let mut next_vec = vec![];
for node in &nodes_to_explore {
next_vec.extend(node.nodes.iter());
next_vec.extend(node.floating_nodes.iter());

let root_node = find_parent_of(root_node, node);

let (pos_x, size_x) = if let Some(root_node) = root_node {
if root_node.layout == NodeLayout::Tabbed {
(node.rect.x + node.deco_rect.x, node.deco_rect.width)
} else {
(node.rect.x, node.rect.width)
}
} else {
(node.rect.x, node.rect.width)
};

let pos_y = if let Some(root_node) = root_node {
if root_node.layout == NodeLayout::Stacked {
root_node.rect.y + node.deco_rect.y
} else {
node.rect.y - node.deco_rect.height
}
} else {
node.rect.y - node.deco_rect.height
};

let window = DesktopWindow {
id: node.id,
x_window_id: node.window,
pos: (pos_x, pos_y),
size: (size_x, (node.rect.height + node.deco_rect.height)),
is_focused: node.focused,
};
debug!("Found {:?}", window);
windows.push(window);
}
nodes_to_explore = next_vec;
}
Ok(windows)
}

/// Return a list of all windows.
pub fn get_windows() -> Result<Vec<DesktopWindow>> {
// Establish a connection to Sway over a Unix socket
let mut connection = Connection::new().expect("Couldn't acquire Sway IPC connection");
let workspaces = connection
.get_workspaces()
.expect("Problem communicating with IPC");
let visible_workspaces = workspaces.iter().filter(|w| w.visible);
let root_node = connection.get_tree()?;
let mut windows = vec![];
for workspace in visible_workspaces {
windows.extend(crawl_windows(&root_node, workspace)?);
}
Ok(windows)
}

/// Focus a specific `window`.
pub fn focus_window(window: &DesktopWindow) -> Result<()> {
let mut connection = Connection::new().expect("Couldn't acquire Sway IPC connection");
let command_str = format!("[con_id=\"{}\"] focus", window.id);
let command = connection
.run_command(&command_str)
.context("Couldn't communicate with Sway")?;
info!("Sending to Sway: {:?}", command);
Ok(())
}