diff --git a/Cargo.toml b/Cargo.toml index e38b3d9..47ed31e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ serde = { version = "1.0.117", features = ["derive"] } serde_json = "1.0.59" clap = "3.0.0-beta.2" users = "0.11.0" +swayipc = "2.7.2" \ No newline at end of file diff --git a/src/bin/swayrd.rs b/src/bin/swayrd.rs index 5c2c616..7cf86d3 100644 --- a/src/bin/swayrd.rs +++ b/src/bin/swayrd.rs @@ -11,12 +11,12 @@ use swayr::demon; use swayr::ipc; fn main() { - let con_props: Arc>> = + let con_props: Arc>> = Arc::new(RwLock::new(HashMap::new())); let con_props_for_ev_handler = con_props.clone(); thread::spawn(move || { - demon::monitor_con_events(con_props_for_ev_handler); + demon::monitor_sway_events(con_props_for_ev_handler); }); demon::serve_client_requests(con_props); diff --git a/src/client.rs b/src/client.rs index 8704062..8257b00 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,11 +1,14 @@ //! Functions and data structures of the swayr client. use crate::con; -use crate::ipc; use crate::util; + use clap::Clap; use std::fmt; +use swayipc as s; +use swayipc::reply as r; + #[derive(Clap, Debug)] pub enum SwayrCommand { /// Switch to next urgent window (if any) or to last recently used window. @@ -74,16 +77,23 @@ pub fn exec_swayr_cmd(cmd: &SwayrCommand) { } } -fn focus_window_by_id(id: ipc::Id) { +fn focus_window_by_id(id: i64) { util::swaymsg(&[format!("[con_id={}]", id).as_str(), "focus"]); } -fn quit_window_by_id(id: ipc::Id) { +fn quit_window_by_id(id: i64) { util::swaymsg(&[format!("[con_id={}]", id).as_str(), "kill"]); } +fn get_tree() -> r::Node { + match s::Connection::new() { + Ok(mut con) => con.get_tree().expect("Got no root node"), + Err(err) => panic!(err), + } +} + pub fn switch_to_urgent_or_lru_window() { - let root = con::get_tree(); + let root = get_tree(); let windows = con::get_windows(&root, true); if let Some(win) = windows .iter() @@ -98,7 +108,7 @@ pub fn switch_to_urgent_or_lru_window() { } pub fn switch_window() { - let root = con::get_tree(); + let root = get_tree(); let windows = con::get_windows(&root, true); if let Some(window) = con::select_window("Switch to window", &windows) { @@ -112,7 +122,7 @@ pub enum Direction { } pub fn focus_next_window_in_direction(dir: Direction) { - let root = con::get_tree(); + let root = get_tree(); let windows = con::get_windows(&root, false); if windows.len() < 2 { @@ -144,7 +154,7 @@ pub fn focus_next_window_in_direction(dir: Direction) { } pub fn switch_workspace() { - let root = con::get_tree(); + let root = get_tree(); let workspaces = con::get_workspaces(&root, false); if let Some(workspace) = @@ -155,7 +165,7 @@ pub fn switch_workspace() { } pub fn switch_workspace_or_window() { - let root = con::get_tree(); + let root = get_tree(); let workspaces = con::get_workspaces(&root, false); let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); if let Some(ws_or_win) = con::select_workspace_or_window( @@ -172,7 +182,7 @@ pub fn switch_workspace_or_window() { } pub fn quit_window() { - let root = con::get_tree(); + let root = get_tree(); let windows = con::get_windows(&root, true); if let Some(window) = con::select_window("Quit window", &windows) { @@ -181,7 +191,7 @@ pub fn quit_window() { } pub fn quit_workspace_or_window() { - let root = con::get_tree(); + let root = get_tree(); let workspaces = con::get_workspaces(&root, false); let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); if let Some(ws_or_win) = diff --git a/src/con.rs b/src/con.rs index d0f5b8e..68cec4f 100644 --- a/src/con.rs +++ b/src/con.rs @@ -2,46 +2,22 @@ use crate::ipc; use crate::util; +use ipc::NodeMethods; 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); - } -} +use swayipc::reply as r; #[derive(Debug)] pub struct Window<'a> { - node: &'a ipc::Node, - workspace: &'a ipc::Node, - con_props: Option, + node: &'a r::Node, + workspace: &'a r::Node, + con_props: Option, } impl Window<'_> { - pub fn get_id(&self) -> ipc::Id { + pub fn get_id(&self) -> i64 { self.node.id } @@ -131,8 +107,8 @@ impl<'a> fmt::Display for Window<'a> { } fn build_windows( - root: &ipc::Node, - mut con_props: HashMap, + root: &r::Node, + mut con_props: HashMap, ) -> Vec { let mut v = vec![]; for workspace in root.workspaces() { @@ -148,8 +124,8 @@ fn build_windows( } fn build_workspaces( - root: &ipc::Node, - mut con_props: HashMap, + root: &r::Node, + mut con_props: HashMap, ) -> Vec { let mut v = vec![]; for workspace in root.workspaces() { @@ -173,8 +149,7 @@ fn build_workspaces( v } -fn get_con_props() -> Result, serde_json::Error> -{ +fn get_con_props() -> Result, serde_json::Error> { if let Ok(sock) = UnixStream::connect(util::get_swayr_socket_path()) { serde_json::from_reader(sock) } else { @@ -183,7 +158,7 @@ fn get_con_props() -> Result, serde_json::Error> } /// Gets all application windows of the tree. -pub fn get_windows(root: &ipc::Node, sort: bool) -> Vec { +pub fn get_windows(root: &r::Node, sort: bool) -> Vec { let con_props = if sort { match get_con_props() { Ok(con_props) => Some(con_props), @@ -205,7 +180,7 @@ pub fn get_windows(root: &ipc::Node, sort: bool) -> Vec { /// Gets all application windows of the tree. pub fn get_workspaces( - root: &ipc::Node, + root: &r::Node, include_scratchpad: bool, ) -> Vec { let con_props = match get_con_props() { @@ -229,18 +204,6 @@ pub fn get_workspaces( workspaces } -#[test] -fn test_get_windows() { - let root = get_tree(); - let cons = get_windows(&root, true); - - println!("There are {} cons.", cons.len()); - - for c in cons { - println!(" {}", c); - } -} - pub fn select_window<'a>( prompt: &'a str, windows: &'a [Window], @@ -295,8 +258,8 @@ pub fn select_workspace_or_window<'a>( } pub struct Workspace<'a> { - node: &'a ipc::Node, - con_props: Option, + node: &'a r::Node, + con_props: Option, pub windows: Vec>, } @@ -305,7 +268,7 @@ impl Workspace<'_> { self.node.name.as_ref().unwrap() } - pub fn get_id(&self) -> ipc::Id { + pub fn get_id(&self) -> i64 { self.node.id } diff --git a/src/demon.rs b/src/demon.rs index caaa3c1..b2206b2 100644 --- a/src/demon.rs +++ b/src/demon.rs @@ -2,116 +2,123 @@ use crate::ipc; use crate::util; -use serde_json::Deserializer; + use std::collections::HashMap; use std::io::Write; use std::os::unix::net::{UnixListener, UnixStream}; -use std::process as proc; use std::sync::Arc; use std::sync::RwLock; use std::time::{SystemTime, UNIX_EPOCH}; -pub fn monitor_con_events( - con_props: Arc>>, +use swayipc as s; +use swayipc::reply as r; + +pub fn monitor_sway_events( + extra_props: Arc>>, ) { - let mut child = proc::Command::new("swaymsg") - .arg("--monitor") - .arg("--raw") - .arg("-t") - .arg("subscribe") - .arg("[\"window\", \"workspace\"]") - .stdout(proc::Stdio::piped()) - .spawn() - .expect("Failed to subscribe to window events"); - let stdout = child.stdout.take().unwrap(); - let reader = std::io::BufReader::new(stdout); - let deserializer = Deserializer::from_reader(reader); - for res in deserializer.into_iter::() { - match res { - Ok(win_ev) => handle_con_event(win_ev, con_props.clone()), - Err(err) => eprintln!("Error handling window event:\n{:?}", err), + let iter = s::Connection::new() + .expect("Could not connect!") + .subscribe(&[s::EventType::Window, s::EventType::Workspace]) + .expect("Could not subscribe to window and workspace events."); + + for ev_result in iter { + let handled; + match ev_result { + Ok(ev) => match ev { + r::Event::Window(win_ev) => { + let extra_props_clone = extra_props.clone(); + handled = handle_window_event(win_ev, extra_props_clone); + } + r::Event::Workspace(ws_ev) => { + let extra_props_clone = extra_props.clone(); + handled = handle_workspace_event(ws_ev, extra_props_clone); + } + _ => handled = false, + }, + Err(e) => { + eprintln!("Error while receiving events: {}", e); + handled = false; + } + } + if handled { + println!( + "New extra_props state:\n{:#?}", + *extra_props.read().unwrap() + ); } } +} - match child.try_wait() { - Ok(exit_code) => match exit_code { - None => { - eprintln!("Stopped monitoring con events. Restarting..."); - monitor_con_events(con_props) - } - Some(exit_code) => { - println!("Swaymsg exited with code {}. Exiting.", exit_code) - } - }, - Err(err) => println!("Swaymsg errored with {}. Exiting.", err), +fn handle_window_event( + ev: Box, + extra_props: Arc>>, +) -> bool { + let r::WindowEvent { change, container } = *ev; + match change { + r::WindowChange::New | r::WindowChange::Focus => { + update_last_focus_time(container.id, extra_props); + true + } + r::WindowChange::Close => { + remove_extra_props(container.id, extra_props); + true + } + _ => false, + } +} + +fn handle_workspace_event( + ev: Box, + extra_props: Arc>>, +) -> bool { + let r::WorkspaceEvent { + change, + current, + old: _, + } = *ev; + match change { + r::WorkspaceChange::Init | r::WorkspaceChange::Focus => { + update_last_focus_time( + current + .expect("No current in Init or Focus workspace event") + .id, + extra_props, + ); + true + } + r::WorkspaceChange::Empty => { + remove_extra_props( + current.expect("No current in Empty workspace event").id, + extra_props, + ); + false + } + _ => false, } } fn update_last_focus_time( - id: ipc::Id, - con_props: Arc>>, + id: i64, + extra_props: Arc>>, ) { - let mut write_lock = con_props.write().unwrap(); + let mut write_lock = extra_props.write().unwrap(); if let Some(mut wp) = write_lock.get_mut(&id) { wp.last_focus_time = get_epoch_time_as_millis(); } else { write_lock.insert( id, - ipc::ConProps { + ipc::ExtraProps { last_focus_time: get_epoch_time_as_millis(), }, ); } } -fn remove_con_props( - id: ipc::Id, - con_props: Arc>>, +fn remove_extra_props( + id: i64, + extra_props: Arc>>, ) { - con_props.write().unwrap().remove(&id); -} - -fn handle_con_event( - ev: ipc::ConEvent, - con_props: Arc>>, -) { - let mut handled = true; - let con_props2 = con_props.clone(); - - match ev { - ipc::ConEvent::WindowEvent { change, container } => match change { - ipc::WindowEventType::New | ipc::WindowEventType::Focus => { - update_last_focus_time(container.id, con_props) - } - ipc::WindowEventType::Close => { - remove_con_props(container.id, con_props) - } - _ => handled = false, - }, - ipc::ConEvent::WorkspaceEvent { - change, - current, - old: _, - } => match change { - ipc::WorkspaceEventType::Init | ipc::WorkspaceEventType::Focus => { - update_last_focus_time( - current - .expect("No current in Init or Focus workspace event") - .id, - con_props, - ) - } - ipc::WorkspaceEventType::Empty => remove_con_props( - current.expect("No current in Empty workspace event").id, - con_props, - ), - _ => handled = false, - }, - } - - if handled { - println!("New con_props state:\n{:#?}", *con_props2.read().unwrap()); - } + extra_props.write().unwrap().remove(&id); } fn get_epoch_time_as_millis() -> u128 { @@ -122,7 +129,7 @@ fn get_epoch_time_as_millis() -> u128 { } pub fn serve_client_requests( - con_props: Arc>>, + extra_props: Arc>>, ) { match std::fs::remove_file(util::get_swayr_socket_path()) { Ok(()) => println!("Deleted stale socket from previous run."), @@ -134,7 +141,7 @@ pub fn serve_client_requests( for stream in listener.incoming() { match stream { Ok(stream) => { - handle_client_request(stream, con_props.clone()); + handle_client_request(stream, extra_props.clone()); } Err(err) => { eprintln!("Error handling client request: {}", err); @@ -151,9 +158,9 @@ pub fn serve_client_requests( fn handle_client_request( mut stream: UnixStream, - con_props: Arc>>, + extra_props: Arc>>, ) { - let json = serde_json::to_string(&*con_props.read().unwrap()).unwrap(); + let json = serde_json::to_string(&*extra_props.read().unwrap()).unwrap(); if let Err(err) = stream.write_all(json.as_bytes()) { eprintln!("Error writing to client: {:?}", err); } diff --git a/src/ipc.rs b/src/ipc.rs index 27f0b0c..926d2d7 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,168 +1,28 @@ -//! Functions and data structures concerned with sway JSON IPC. +//! Extensions of swayipc types and IPC structs. extern crate serde; extern crate serde_json; +extern crate swayipc; extern crate users; use serde::{Deserialize, Serialize}; +use swayipc::reply as r; -pub type Id = u32; -pub type Dim = u16; -pub type Pos = i16; // Position can be off-screen, so i16 instead of u16. -pub type Pid = u32; - -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub struct Rect { - pub x: Pos, - pub y: Pos, - pub width: Dim, - pub height: Dim, -} - -// TODO: Maybe there are more? -#[derive(Deserialize, Debug)] -pub enum Border { - #[serde(rename = "normal")] - Normal, - #[serde(rename = "none")] - None, - #[serde(rename = "pixel")] - Pixel, - #[serde(rename = "csd")] - Csd, -} - -// TODO: Maybe there are more? -#[derive(Deserialize, Debug)] -pub enum Layout { - #[serde(rename = "splith")] - SplitH, - #[serde(rename = "splitv")] - SplitV, - #[serde(rename = "tabbed")] - Tabbed, - #[serde(rename = "stacked")] - Stacked, - #[serde(rename = "output")] - Output, - #[serde(rename = "none")] - None, -} - -#[derive(Deserialize, Debug)] -pub enum Orientation { - #[serde(rename = "horizontal")] - Horizontal, - #[serde(rename = "vertical")] - Vertical, - #[serde(rename = "none")] - None, -} - -#[derive(Deserialize, PartialEq, Debug)] -pub enum NodeType { - #[serde(rename = "root")] - Root, - #[serde(rename = "workspace")] - Workspace, - #[serde(rename = "output")] - Output, - #[serde(rename = "con")] - Con, - #[serde(rename = "floating_con")] - FloatingCon, -} - -#[derive(Deserialize, Debug)] -pub enum ShellType { - #[serde(rename = "xdg_shell")] - XdgShell, - #[serde(rename = "xwayland")] - XWayland, -} - -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub struct WindowProperties { - pub class: Option, - pub instance: Option, - pub title: Option, - //pub window_type: Option - //pub transient_for: DONTKNOW, -} - -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub struct Node { - pub id: Id, - pub name: Option, - pub rect: Rect, - pub focused: bool, - pub focus: Vec, - pub border: Border, - pub current_border_width: Dim, - pub layout: Layout, - pub orientation: Orientation, - pub percent: Option, - pub window_rect: Rect, - pub deco_rect: Rect, - pub geometry: Rect, - pub window: Option, - pub urgent: bool, - pub marks: Vec, - pub fullscreen_mode: u8, // TODO: actually, it's 0 or 1, i.e., a bool - pub nodes: Vec, - pub floating_nodes: Vec, - pub sticky: bool, - pub r#type: NodeType, - pub app_id: Option, - pub visible: Option, - pub max_render_time: Option, - pub pid: Option, - pub shell: Option, - pub window_properties: Option, -} - -impl Node { - pub fn iter(&self) -> NodeIter { - NodeIter::new(self) - } - - pub fn windows(&self) -> Vec<&Node> { - self.iter() - .filter(|n| { - (n.r#type == NodeType::Con || n.r#type == NodeType::FloatingCon) - && n.name.is_some() - }) - .collect() - } - - pub fn workspaces(&self) -> Vec<&Node> { - self.iter() - .filter(|n| n.r#type == NodeType::Workspace) - .collect() - } - - pub fn outputs(&self) -> Vec<&Node> { - self.iter() - .filter(|n| n.r#type == NodeType::Output) - .collect() - } -} - +/// Immutable Node Iterator +/// +/// Iterates nodes in depth-first order, tiled nodes before floating nodes. pub struct NodeIter<'a> { - stack: Vec<&'a Node>, + stack: Vec<&'a r::Node>, } impl<'a> NodeIter<'a> { - fn new(node: &'a Node) -> NodeIter { + pub fn new(node: &'a r::Node) -> NodeIter { NodeIter { stack: vec![node] } } } impl<'a> Iterator for NodeIter<'a> { - type Item = &'a Node; + type Item = &'a r::Node; fn next(&mut self) -> Option { if let Some(node) = self.stack.pop() { @@ -179,67 +39,43 @@ impl<'a> Iterator for NodeIter<'a> { } } -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub enum WindowEventType { - #[serde(rename = "new")] - New, - #[serde(rename = "close")] - Close, - #[serde(rename = "focus")] - Focus, - #[serde(rename = "title")] - Title, - #[serde(rename = "fullscreen_mode")] - FullscreenMode, - #[serde(rename = "move")] - Move, - #[serde(rename = "floating")] - Floating, - #[serde(rename = "urgent")] - Urgent, - #[serde(rename = "mark")] - Mark, -} +/// Extension methods for [`swayipc::reply::Node`]. +pub trait NodeMethods { + /// Returns an iterator for this [`swayipc::reply::Node`] and its childres. + fn iter(&self) -> NodeIter; + + /// Returns all nodes being application windows. + fn windows(&self) -> Vec<&r::Node>; -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -pub enum WorkspaceEventType { - #[serde(rename = "init")] - Init, - #[serde(rename = "empty")] - Empty, - #[serde(rename = "focus")] - Focus, - #[serde(rename = "move")] - Move, - #[serde(rename = "rename")] - Rename, - #[serde(rename = "urgent")] - Urgent, - #[serde(rename = "reload")] - Reload, + /// Returns all nodes being workspaces. + fn workspaces(&self) -> Vec<&r::Node>; } -#[derive(Deserialize, Debug)] -#[serde(untagged)] -#[allow(dead_code)] -pub enum ConEvent { - WindowEvent { - change: WindowEventType, - container: Box, - }, - WorkspaceEvent { - change: WorkspaceEventType, - // On a reload event, current and old are both null. - current: Option>, - // If the old was empty, it'll be killed => "old": null. - old: Option>, - }, +impl NodeMethods for r::Node { + fn iter(&self) -> NodeIter { + NodeIter::new(self) + } + + fn windows(&self) -> Vec<&r::Node> { + self.iter() + .filter(|n| { + (n.node_type == r::NodeType::Con + || n.node_type == r::NodeType::FloatingCon) + && n.name.is_some() + }) + .collect() + } + + fn workspaces(&self) -> Vec<&r::Node> { + self.iter() + .filter(|n| n.node_type == r::NodeType::Workspace) + .collect() + } } +/// Extra properties gathered by swayrd for windows and workspaces. #[derive(Debug, Deserialize, Serialize)] -pub struct ConProps { +pub struct ExtraProps { /// Milliseconds since UNIX epoch. pub last_focus_time: u128, }