From 5c348fb7c42b5c4abdb54c8f8a1b642d7d6ad722 Mon Sep 17 00:00:00 2001 From: Tassilo Horn Date: Tue, 16 Feb 2021 22:08:06 +0100 Subject: [PATCH] Execute commands in the demon instead of the client --- src/bin/swayr.rs | 7 +- src/client.rs | 330 ++--------------------------------------------- src/cmds.rs | 329 ++++++++++++++++++++++++++++++++++++++++++++++ src/con.rs | 70 ++++------ src/demon.rs | 20 ++- src/ipc.rs | 27 +++- src/lib.rs | 1 + 7 files changed, 409 insertions(+), 375 deletions(-) create mode 100644 src/cmds.rs diff --git a/src/bin/swayr.rs b/src/bin/swayr.rs index c98fa85..0052e94 100644 --- a/src/bin/swayr.rs +++ b/src/bin/swayr.rs @@ -4,6 +4,7 @@ extern crate clap; use clap::Clap; use swayr::client; +use swayr::ipc; /// Windows are sorted urgent first, then windows in LRU order, focused window /// last. Licensed under the GPLv3 (or later). @@ -15,10 +16,12 @@ use swayr::client; )] struct Opts { #[clap(subcommand)] - command: client::SwayrCommand, + command: ipc::SwayrCommand, } fn main() { let opts: Opts = Opts::parse(); - client::exec_swayr_cmd(&opts.command); + if let Err(err) = client::send_swayr_cmd(opts.command) { + eprintln!("Could not send command: {}", err); + } } diff --git a/src/client.rs b/src/client.rs index 8257b00..8a7d5b8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,323 +1,13 @@ -//! Functions and data structures of the swayr client. +extern crate serde_json; -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. - 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, -} - -impl fmt::Display for SwayrCommand { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{:?}", self) - } -} - -pub fn exec_swayr_cmd(cmd: &SwayrCommand) { - match cmd { - SwayrCommand::SwitchToUrgentOrLRUWindow => { - switch_to_urgent_or_lru_window() - } - SwayrCommand::SwitchWindow => switch_window(), - SwayrCommand::NextWindow => { - focus_next_window_in_direction(Direction::Forward) - } - SwayrCommand::PrevWindow => { - focus_next_window_in_direction(Direction::Backward) - } - SwayrCommand::QuitWindow => quit_window(), - SwayrCommand::SwitchWorkspace => switch_workspace(), - SwayrCommand::SwitchWorkspaceOrWindow => switch_workspace_or_window(), - SwayrCommand::QuitWorkspaceOrWindow => quit_workspace_or_window(), - SwayrCommand::ExecuteSwaymsgCommand => exec_swaymsg_command(), - SwayrCommand::ExecuteSwayrCommand => { - if let Some(c) = util::wofi_select( - "Select swayr command", - &[ - SwayrCommand::ExecuteSwaymsgCommand, - SwayrCommand::QuitWindow, - SwayrCommand::QuitWorkspaceOrWindow, - SwayrCommand::SwitchWindow, - SwayrCommand::SwitchWorkspace, - SwayrCommand::SwitchWorkspaceOrWindow, - SwayrCommand::SwitchToUrgentOrLRUWindow, - SwayrCommand::NextWindow, - SwayrCommand::PrevWindow, - ], - ) { - exec_swayr_cmd(c); - } - } - } -} - -fn focus_window_by_id(id: i64) { - util::swaymsg(&[format!("[con_id={}]", id).as_str(), "focus"]); -} - -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 = get_tree(); - let windows = con::get_windows(&root, true); - if let Some(win) = windows - .iter() - .find(|w| w.is_urgent()) - .or_else(|| windows.get(0)) - { - println!("Switching to {}", win); - focus_window_by_id(win.get_id()) - } else { - println!("No window to switch to.") - } -} - -pub fn switch_window() { - let root = get_tree(); - let windows = con::get_windows(&root, true); - - if let Some(window) = con::select_window("Switch to window", &windows) { - focus_window_by_id(window.get_id()) - } -} - -pub enum Direction { - Backward, - Forward, -} - -pub fn focus_next_window_in_direction(dir: Direction) { - let root = get_tree(); - let windows = con::get_windows(&root, false); - - if windows.len() < 2 { - return; - } - - let pred: Box bool> = - if windows.iter().find(|w| w.is_focused()).is_none() { - let last_focused_win_id = - con::get_windows(&root, true).get(0).unwrap().get_id(); - Box::new(move |w| w.get_id() == last_focused_win_id) - } else { - Box::new(|w: &con::Window| w.is_focused()) - }; - - let mut iter: Box> = match dir { - Direction::Forward => Box::new(windows.iter().rev().cycle()), - Direction::Backward => Box::new(windows.iter().cycle()), - }; - - loop { - let win = iter.next().unwrap(); - if pred(win) { - let win = iter.next().unwrap(); - focus_window_by_id(win.get_id()); - return; - } - } -} - -pub fn switch_workspace() { - let root = get_tree(); - let workspaces = con::get_workspaces(&root, false); - - if let Some(workspace) = - con::select_workspace("Switch to workspace", &workspaces) - { - util::swaymsg(&["workspace", "number", workspace.get_name()]); - } -} - -pub fn switch_workspace_or_window() { - 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( - "Select workspace or window", - &ws_or_wins, - ) { - match ws_or_win { - con::WsOrWin::Ws { ws } => { - util::swaymsg(&["workspace", "number", ws.get_name()]); - } - con::WsOrWin::Win { win } => focus_window_by_id(win.get_id()), - } - } -} - -pub fn quit_window() { - let root = get_tree(); - let windows = con::get_windows(&root, true); - - if let Some(window) = con::select_window("Quit window", &windows) { - quit_window_by_id(window.get_id()) - } -} - -pub fn quit_workspace_or_window() { - 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("Quit workspace or window", &ws_or_wins) - { - match ws_or_win { - con::WsOrWin::Ws { ws } => { - for win in &ws.windows { - quit_window_by_id(win.get_id()) - } - } - con::WsOrWin::Win { win } => quit_window_by_id(win.get_id()), - } - } -} - -fn get_swaymsg_commands<'a>() -> Vec> { - let mut cmds = vec![]; - cmds.push(vec!["exit"]); - cmds.push(vec!["floating", "toggle"]); - cmds.push(vec!["focus", "child"]); - cmds.push(vec!["focus", "parent"]); - - for b in &["none", "normal", "csd", "pixel"] { - cmds.push(vec!["border", b]); - } - - cmds.push(vec!["focus", "tiling"]); - cmds.push(vec!["focus", "floating"]); - cmds.push(vec!["focus", "mode_toggle"]); - - cmds.push(vec!["fullscreen", "toggle"]); - - for x in &["focus", "fullscreen", "open", "none", "visible"] { - cmds.push(vec!["inhibit_idle", x]) - } - - for l in &["default", "splith", "splitv", "stacking", "tiling"] { - cmds.push(vec!["layout", l]) - } - - cmds.push(vec!["reload"]); - - for e in &["enable", "disable"] { - cmds.push(vec!["shortcuts", "inhibitor", e]) - } - - cmds.push(vec!["sticky", "toggle"]); - - for x in &["yes", "no", "always"] { - cmds.push(vec!["focus_follows_mouse", x]) - } - - for x in &["smart", "urgent", "focus", "none"] { - cmds.push(vec!["focus_on_window_activation", x]) - } - - for x in &["yes", "no", "force", "workspace"] { - cmds.push(vec!["focus_wrapping", x]) - } - - for x in &[ - "none", - "vertical", - "horizontal", - "both", - "smart", - "smart_no_gaps", - ] { - cmds.push(vec!["hide_edge_borders", x]) - } - - cmds.push(vec!["kill"]); - - for x in &["on", "no_gaps", "off"] { - cmds.push(vec!["smart_borders", x]) - } - - for x in &["on", "off"] { - cmds.push(vec!["smart_gaps", x]) - } - - for x in &["output", "container", "none"] { - cmds.push(vec!["mouse_warping", x]) - } - - for x in &["smart", "ignore", "leave_fullscreen"] { - cmds.push(vec!["popup_during_fullscreen", x]) - } - - for x in &["yes", "no"] { - cmds.push(vec!["show_marks", x]); - cmds.push(vec!["workspace_auto_back_and_forth", x]); - } - - cmds.push(vec!["tiling_drag", "toggle"]); - - for x in &["left", "center", "right"] { - cmds.push(vec!["title_align", x]); - } - - for x in &["enable", "disable", "allow", "deny"] { - cmds.push(vec!["urgent", x]) - } - - cmds.sort(); - - cmds.iter() - .map(|v| SwaymsgCmd { cmd: v.to_vec() }) - .collect() -} - -struct SwaymsgCmd<'a> { - cmd: Vec<&'a str>, -} - -impl<'a> fmt::Display for SwaymsgCmd<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.cmd.join(" ")) - } -} - -pub fn exec_swaymsg_command() { - let cmds = get_swaymsg_commands(); - let cmd = util::wofi_select("Execute swaymsg command", &cmds); - if let Some(cmd) = cmd { - util::swaymsg(&cmd.cmd); - } +use std::io::Write; +use std::os::unix::net::UnixStream; + +pub fn send_swayr_cmd( + cmd: ipc::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 new file mode 100644 index 0000000..c71f6c6 --- /dev/null +++ b/src/cmds.rs @@ -0,0 +1,329 @@ +//! Functions and data structures of the swayr client. + +use crate::con; +use crate::ipc; +use crate::ipc::SwayrCommand; +use crate::util; + +use std::collections::HashMap; +use std::fmt; +use std::sync::Arc; +use std::sync::RwLock; + +use swayipc as s; +use swayipc::reply as r; + +impl fmt::Display for SwayrCommand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{:?}", self) + } +} + +pub fn exec_swayr_cmd( + cmd: &SwayrCommand, + extra_props: Arc>>, +) { + match cmd { + SwayrCommand::SwitchToUrgentOrLRUWindow => { + switch_to_urgent_or_lru_window(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::SwitchWindow => { + switch_window(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::NextWindow => focus_next_window_in_direction( + Direction::Forward, + Some(&*extra_props.read().unwrap()), + ), + SwayrCommand::PrevWindow => focus_next_window_in_direction( + Direction::Backward, + Some(&*extra_props.read().unwrap()), + ), + SwayrCommand::QuitWindow => { + quit_window(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::SwitchWorkspace => { + switch_workspace(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::SwitchWorkspaceOrWindow => { + switch_workspace_or_window(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::QuitWorkspaceOrWindow => { + quit_workspace_or_window(Some(&*extra_props.read().unwrap())) + } + SwayrCommand::ExecuteSwaymsgCommand => exec_swaymsg_command(), + SwayrCommand::ExecuteSwayrCommand => { + if let Some(c) = util::wofi_select( + "Select swayr command", + &[ + SwayrCommand::ExecuteSwaymsgCommand, + SwayrCommand::QuitWindow, + SwayrCommand::QuitWorkspaceOrWindow, + SwayrCommand::SwitchWindow, + SwayrCommand::SwitchWorkspace, + SwayrCommand::SwitchWorkspaceOrWindow, + SwayrCommand::SwitchToUrgentOrLRUWindow, + SwayrCommand::NextWindow, + SwayrCommand::PrevWindow, + ], + ) { + exec_swayr_cmd(c, extra_props); + } + } + } +} + +fn focus_window_by_id(id: i64) { + util::swaymsg(&[format!("[con_id={}]", id).as_str(), "focus"]); +} + +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( + extra_props: Option<&HashMap>, +) { + let root = get_tree(); + let windows = con::get_windows(&root, extra_props); + if let Some(win) = windows + .iter() + .find(|w| w.is_urgent()) + .or_else(|| windows.get(0)) + { + println!("Switching to {}", win); + focus_window_by_id(win.get_id()) + } else { + println!("No window to switch to.") + } +} + +pub fn switch_window(extra_props: Option<&HashMap>) { + let root = get_tree(); + let windows = con::get_windows(&root, extra_props); + + if let Some(window) = con::select_window("Switch to window", &windows) { + focus_window_by_id(window.get_id()) + } +} + +pub enum Direction { + Backward, + Forward, +} + +pub fn focus_next_window_in_direction( + dir: Direction, + extra_props: Option<&HashMap>, +) { + let root = get_tree(); + let windows = con::get_windows(&root, None); + + if windows.len() < 2 { + return; + } + + let pred: Box bool> = + if windows.iter().find(|w| w.is_focused()).is_none() { + let last_focused_win_id = con::get_windows(&root, extra_props) + .get(0) + .unwrap() + .get_id(); + Box::new(move |w| w.get_id() == last_focused_win_id) + } else { + Box::new(|w: &con::Window| w.is_focused()) + }; + + let mut iter: Box> = match dir { + Direction::Forward => Box::new(windows.iter().rev().cycle()), + Direction::Backward => Box::new(windows.iter().cycle()), + }; + + loop { + let win = iter.next().unwrap(); + if pred(win) { + let win = iter.next().unwrap(); + focus_window_by_id(win.get_id()); + return; + } + } +} + +pub fn switch_workspace(extra_props: Option<&HashMap>) { + let root = get_tree(); + let workspaces = con::get_workspaces(&root, false, extra_props); + + if let Some(workspace) = + con::select_workspace("Switch to workspace", &workspaces) + { + util::swaymsg(&["workspace", "number", workspace.get_name()]); + } +} + +pub fn switch_workspace_or_window( + extra_props: Option<&HashMap>, +) { + let root = get_tree(); + let workspaces = con::get_workspaces(&root, false, extra_props); + let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); + if let Some(ws_or_win) = con::select_workspace_or_window( + "Select workspace or window", + &ws_or_wins, + ) { + match ws_or_win { + con::WsOrWin::Ws { ws } => { + util::swaymsg(&["workspace", "number", ws.get_name()]); + } + con::WsOrWin::Win { win } => focus_window_by_id(win.get_id()), + } + } +} + +pub fn quit_window(extra_props: Option<&HashMap>) { + let root = get_tree(); + let windows = con::get_windows(&root, extra_props); + + if let Some(window) = con::select_window("Quit window", &windows) { + quit_window_by_id(window.get_id()) + } +} + +pub fn quit_workspace_or_window( + extra_props: Option<&HashMap>, +) { + let root = get_tree(); + let workspaces = con::get_workspaces(&root, false, extra_props); + let ws_or_wins = con::WsOrWin::from_workspaces(&workspaces); + if let Some(ws_or_win) = + con::select_workspace_or_window("Quit workspace or window", &ws_or_wins) + { + match ws_or_win { + con::WsOrWin::Ws { ws } => { + for win in &ws.windows { + quit_window_by_id(win.get_id()) + } + } + con::WsOrWin::Win { win } => quit_window_by_id(win.get_id()), + } + } +} + +fn get_swaymsg_commands<'a>() -> Vec> { + let mut cmds = vec![]; + cmds.push(vec!["exit"]); + cmds.push(vec!["floating", "toggle"]); + cmds.push(vec!["focus", "child"]); + cmds.push(vec!["focus", "parent"]); + + for b in &["none", "normal", "csd", "pixel"] { + cmds.push(vec!["border", b]); + } + + cmds.push(vec!["focus", "tiling"]); + cmds.push(vec!["focus", "floating"]); + cmds.push(vec!["focus", "mode_toggle"]); + + cmds.push(vec!["fullscreen", "toggle"]); + + for x in &["focus", "fullscreen", "open", "none", "visible"] { + cmds.push(vec!["inhibit_idle", x]) + } + + for l in &["default", "splith", "splitv", "stacking", "tiling"] { + cmds.push(vec!["layout", l]) + } + + cmds.push(vec!["reload"]); + + for e in &["enable", "disable"] { + cmds.push(vec!["shortcuts", "inhibitor", e]) + } + + cmds.push(vec!["sticky", "toggle"]); + + for x in &["yes", "no", "always"] { + cmds.push(vec!["focus_follows_mouse", x]) + } + + for x in &["smart", "urgent", "focus", "none"] { + cmds.push(vec!["focus_on_window_activation", x]) + } + + for x in &["yes", "no", "force", "workspace"] { + cmds.push(vec!["focus_wrapping", x]) + } + + for x in &[ + "none", + "vertical", + "horizontal", + "both", + "smart", + "smart_no_gaps", + ] { + cmds.push(vec!["hide_edge_borders", x]) + } + + cmds.push(vec!["kill"]); + + for x in &["on", "no_gaps", "off"] { + cmds.push(vec!["smart_borders", x]) + } + + for x in &["on", "off"] { + cmds.push(vec!["smart_gaps", x]) + } + + for x in &["output", "container", "none"] { + cmds.push(vec!["mouse_warping", x]) + } + + for x in &["smart", "ignore", "leave_fullscreen"] { + cmds.push(vec!["popup_during_fullscreen", x]) + } + + for x in &["yes", "no"] { + cmds.push(vec!["show_marks", x]); + cmds.push(vec!["workspace_auto_back_and_forth", x]); + } + + cmds.push(vec!["tiling_drag", "toggle"]); + + for x in &["left", "center", "right"] { + cmds.push(vec!["title_align", x]); + } + + for x in &["enable", "disable", "allow", "deny"] { + cmds.push(vec!["urgent", x]) + } + + cmds.sort(); + + cmds.iter() + .map(|v| SwaymsgCmd { cmd: v.to_vec() }) + .collect() +} + +struct SwaymsgCmd<'a> { + cmd: Vec<&'a str>, +} + +impl<'a> fmt::Display for SwaymsgCmd<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.cmd.join(" ")) + } +} + +pub fn exec_swaymsg_command() { + let cmds = get_swaymsg_commands(); + let cmd = util::wofi_select("Execute swaymsg command", &cmds); + if let Some(cmd) = cmd { + util::swaymsg(&cmd.cmd); + } +} diff --git a/src/con.rs b/src/con.rs index 68cec4f..ac7f9ff 100644 --- a/src/con.rs +++ b/src/con.rs @@ -6,7 +6,6 @@ use ipc::NodeMethods; use std::cmp; use std::collections::HashMap; use std::fmt; -use std::os::unix::net::UnixStream; use swayipc::reply as r; #[derive(Debug)] @@ -106,16 +105,16 @@ impl<'a> fmt::Display for Window<'a> { } } -fn build_windows( - root: &r::Node, - mut con_props: HashMap, -) -> Vec { +fn build_windows<'a>( + root: &'a r::Node, + extra_props: Option<&HashMap>, +) -> Vec> { 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), + con_props: extra_props.and_then(|m| m.get(&n.id).cloned()), workspace: &workspace, }) } @@ -123,10 +122,10 @@ fn build_windows( v } -fn build_workspaces( - root: &r::Node, - mut con_props: HashMap, -) -> Vec { +fn build_workspaces<'a>( + root: &'a r::Node, + extra_props: Option<&HashMap>, +) -> Vec> { let mut v = vec![]; for workspace in root.workspaces() { let mut wins: Vec = workspace @@ -134,14 +133,14 @@ fn build_workspaces( .iter() .map(|w| Window { node: &w, - con_props: con_props.remove(&w.id), + con_props: extra_props.and_then(|m| m.get(&w.id).cloned()), workspace: &workspace, }) .collect(); wins.sort(); v.push(Workspace { node: &workspace, - con_props: con_props.remove(&workspace.id), + con_props: extra_props.and_then(|m| m.get(&workspace.id).cloned()), windows: wins, }) } @@ -149,49 +148,26 @@ fn build_workspaces( v } -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 { - panic!("Could not connect to socket!") - } -} - /// Gets all application windows of the tree. -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), - Err(e) => { - eprintln!("Got no con_props: {:?}", e); - None - } - } - } else { - None - }; - - let mut wins = build_windows(root, con_props.unwrap_or_default()); - if sort { +pub fn get_windows<'a>( + root: &'a r::Node, + extra_props: Option<&HashMap>, +) -> Vec> { + let extra_props_given = extra_props.is_some(); + let mut wins = build_windows(root, extra_props); + if extra_props_given { wins.sort(); } wins } /// Gets all application windows of the tree. -pub fn get_workspaces( - root: &r::Node, +pub fn get_workspaces<'a>( + root: &'a r::Node, include_scratchpad: bool, -) -> Vec { - 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()); + con_props: Option<&HashMap>, +) -> Vec> { + let workspaces = build_workspaces(root, con_props); let mut workspaces = if include_scratchpad { workspaces } else { diff --git a/src/demon.rs b/src/demon.rs index b2206b2..b8be845 100644 --- a/src/demon.rs +++ b/src/demon.rs @@ -1,10 +1,11 @@ //! Functions and data structures of the swayrd demon. +use crate::cmds; use crate::ipc; use crate::util; use std::collections::HashMap; -use std::io::Write; +use std::io::Read; use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::Arc; use std::sync::RwLock; @@ -102,7 +103,7 @@ fn update_last_focus_time( extra_props: Arc>>, ) { let mut write_lock = extra_props.write().unwrap(); - if let Some(mut wp) = write_lock.get_mut(&id) { + if let Some(wp) = write_lock.get_mut(&id) { wp.last_focus_time = get_epoch_time_as_millis(); } else { write_lock.insert( @@ -160,8 +161,17 @@ fn handle_client_request( mut stream: UnixStream, extra_props: Arc>>, ) { - 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); + let mut cmd_str = String::new(); + if stream.read_to_string(&mut cmd_str).is_ok() { + if let Ok(cmd) = serde_json::from_str::(&cmd_str) { + cmds::exec_swayr_cmd(&cmd, extra_props); + } else { + eprintln!( + "Could not serialize following string to SwayrCommand.\n{}", + cmd_str + ); + } + } else { + eprintln!("Could not read command from client."); } } diff --git a/src/ipc.rs b/src/ipc.rs index 926d2d7..9a61ada 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -5,6 +5,7 @@ extern crate serde_json; extern crate swayipc; extern crate users; +use clap::Clap; use serde::{Deserialize, Serialize}; use swayipc::reply as r; @@ -73,8 +74,32 @@ impl NodeMethods for r::Node { } } +#[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, +} + /// Extra properties gathered by swayrd for windows and workspaces. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct ExtraProps { /// Milliseconds since UNIX epoch. pub last_focus_time: u128, diff --git a/src/lib.rs b/src/lib.rs index 3dc667a..b5b9670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! IPC interface. The client `swayr` offers subcommands, see `swayr --help`. pub mod client; +pub mod cmds; pub mod con; pub mod demon; pub mod ipc;