diff --git a/src/bin/swayr.rs b/src/bin/swayr.rs
index c3b5f80..ba5dde3 100644
--- a/src/bin/swayr.rs
+++ b/src/bin/swayr.rs
@@ -27,7 +27,7 @@ use clap::{crate_version, Clap};
)]
struct Opts {
#[clap(subcommand)]
- command: swayr::ipc::SwayrCommand,
+ command: swayr::cmds::SwayrCommand,
}
fn main() {
diff --git a/src/client.rs b/src/client.rs
index e77ca9d..a3a830f 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -13,13 +13,13 @@
// You should have received a copy of the GNU General Public License along with
// this program. If not, see .
-use crate::ipc;
+use crate::cmds;
use crate::util;
use std::io::Write;
use std::os::unix::net::UnixStream;
pub fn send_swayr_cmd(
- cmd: ipc::SwayrCommand,
+ cmd: cmds::SwayrCommand,
) -> std::result::Result<(), std::io::Error> {
let mut sock = UnixStream::connect(util::get_swayr_socket_path())?;
sock.write_all(serde_json::to_string(&cmd).unwrap().as_bytes())
diff --git a/src/cmds.rs b/src/cmds.rs
index f56c970..790a9c8 100644
--- a/src/cmds.rs
+++ b/src/cmds.rs
@@ -17,18 +17,43 @@
use crate::con;
use crate::config as cfg;
-use crate::ipc;
-use crate::ipc::SwayrCommand;
use crate::util;
use crate::util::DisplayFormat;
+use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use swayipc as s;
+use clap::Clap;
+
+#[derive(Clap, Debug, Deserialize, Serialize)]
+pub enum SwayrCommand {
+ /// Switch to next urgent window (if any) or to last recently used window.
+ SwitchToUrgentOrLRUWindow,
+ /// Focus the selected window
+ SwitchWindow,
+ /// Focus the next window.
+ NextWindow,
+ /// Focus the previous window.
+ PrevWindow,
+ /// Quit the selected window
+ QuitWindow,
+ /// Switch to the selected workspace
+ SwitchWorkspace,
+ /// Switch to the selected workspace or focus the selected window
+ SwitchWorkspaceOrWindow,
+ /// Quit all windows of selected workspace or the selected window
+ QuitWorkspaceOrWindow,
+ /// Select and execute a swaymsg command
+ ExecuteSwaymsgCommand,
+ /// Select and execute a swayr command
+ ExecuteSwayrCommand,
+}
+
pub struct ExecSwayrCmdArgs<'a> {
pub cmd: &'a SwayrCommand,
- pub extra_props: Arc>>,
+ pub extra_props: Arc>>,
}
impl DisplayFormat for SwayrCommand {
@@ -98,7 +123,7 @@ fn quit_window_by_id(id: i64) {
run_sway_command(&[format!("[con_id={}]", id).as_str(), "kill"]);
}
-fn get_tree() -> s::Node {
+pub fn get_tree() -> s::Node {
match s::Connection::new() {
Ok(mut con) => con.get_tree().expect("Got no root node"),
Err(err) => panic!("{}", err),
@@ -106,7 +131,7 @@ fn get_tree() -> s::Node {
}
pub fn switch_to_urgent_or_lru_window(
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) {
let root = get_tree();
let windows = con::get_windows(&root, false, extra_props);
@@ -122,7 +147,7 @@ pub fn switch_to_urgent_or_lru_window(
}
}
-pub fn switch_window(extra_props: Option<&HashMap>) {
+pub fn switch_window(extra_props: Option<&HashMap>) {
let root = get_tree();
let windows = con::get_windows(&root, true, extra_props);
@@ -138,7 +163,7 @@ pub enum Direction {
pub fn focus_next_window_in_direction(
dir: Direction,
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) {
let root = get_tree();
let windows = con::get_windows(&root, false, None);
@@ -174,7 +199,7 @@ pub fn focus_next_window_in_direction(
}
}
-pub fn switch_workspace(extra_props: Option<&HashMap>) {
+pub fn switch_workspace(extra_props: Option<&HashMap>) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, false, extra_props);
@@ -186,7 +211,7 @@ pub fn switch_workspace(extra_props: Option<&HashMap>) {
}
pub fn switch_workspace_or_window(
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, true, extra_props);
@@ -204,7 +229,7 @@ pub fn switch_workspace_or_window(
}
}
-pub fn quit_window(extra_props: Option<&HashMap>) {
+pub fn quit_window(extra_props: Option<&HashMap>) {
let root = get_tree();
let windows = con::get_windows(&root, true, extra_props);
@@ -214,7 +239,7 @@ pub fn quit_window(extra_props: Option<&HashMap>) {
}
pub fn quit_workspace_or_window(
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) {
let root = get_tree();
let workspaces = con::get_workspaces(&root, true, extra_props);
diff --git a/src/con.rs b/src/con.rs
index 6f1885a..5651b06 100644
--- a/src/con.rs
+++ b/src/con.rs
@@ -16,20 +16,110 @@
//! Convenience data structures built from the IPC structs.
use crate::config as cfg;
-use crate::ipc;
-use crate::ipc::NodeMethods;
use crate::util;
use crate::util::DisplayFormat;
use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;
use swayipc as s;
+/// 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 {
+ 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
+ }
+ }
+}
+
+/// 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 all nodes being application windows.
+ fn windows(&self) -> Vec<&s::Node>;
+
+ /// Returns all nodes being workspaces.
+ fn workspaces(&self) -> Vec<&s::Node>;
+
+ fn is_scratchpad(&self) -> bool;
+}
+
+impl NodeMethods for s::Node {
+ fn iter(&self) -> NodeIter {
+ 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 is_container(&self) -> bool {
+ self.node_type == s::NodeType::Workspace
+ || self.node_type == s::NodeType::Con
+ && self.name.is_none()
+ && self.layout != s::NodeLayout::None
+ }
+
+ fn windows(&self) -> Vec<&s::Node> {
+ self.iter().filter(|n| n.is_window()).collect()
+ }
+
+ fn workspaces(&self) -> Vec<&s::Node> {
+ self.iter()
+ .filter(|n| n.node_type == s::NodeType::Workspace)
+ .collect()
+ }
+
+ fn is_scratchpad(&self) -> bool {
+ self.name.is_some() && self.name.as_ref().unwrap().eq("__i3_scratch")
+ }
+}
+
+/// Extra properties gathered by swayrd for windows and workspaces.
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct ExtraProps {
+ /// Milliseconds since UNIX epoch.
+ pub last_focus_time: u128,
+}
+
#[derive(Debug)]
pub struct Window<'a> {
node: &'a s::Node,
workspace: &'a s::Node,
- extra_props: Option,
+ extra_props: Option,
}
impl Window<'_> {
@@ -183,7 +273,7 @@ impl<'a> DisplayFormat for Window<'a> {
fn build_windows<'a>(
root: &'a s::Node,
include_scratchpad_windows: bool,
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) -> Vec> {
let mut v = vec![];
for workspace in root.workspaces() {
@@ -205,7 +295,7 @@ fn build_windows<'a>(
fn build_workspaces<'a>(
root: &'a s::Node,
include_scratchpad: bool,
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) -> Vec> {
let mut v = vec![];
for workspace in root.workspaces() {
@@ -240,7 +330,7 @@ fn build_workspaces<'a>(
pub fn get_windows<'a>(
root: &'a s::Node,
include_scratchpad_windows: bool,
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) -> Vec> {
let extra_props_given = extra_props.is_some();
let mut wins = build_windows(root, include_scratchpad_windows, extra_props);
@@ -254,7 +344,7 @@ pub fn get_windows<'a>(
pub fn get_workspaces<'a>(
root: &'a s::Node,
include_scratchpad: bool,
- extra_props: Option<&HashMap>,
+ extra_props: Option<&HashMap>,
) -> Vec> {
let mut workspaces =
build_workspaces(root, include_scratchpad, extra_props);
@@ -314,7 +404,7 @@ pub fn select_workspace_or_window<'a>(
pub struct Workspace<'a> {
node: &'a s::Node,
- extra_props: Option,
+ extra_props: Option,
pub windows: Vec>,
}
diff --git a/src/config.rs b/src/config.rs
index 8383775..e8c101a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -17,6 +17,7 @@
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
use std::fs::DirBuilder;
use std::fs::OpenOptions;
use std::io::{Read, Write};
@@ -26,6 +27,7 @@ use std::path::Path;
pub struct Config {
pub menu: Option