Use swayipc crate instead of home-grown IPC structs

That saves a lot of boring code. :-)
timeout_old
Tassilo Horn 4 years ago
parent 68da44f9f1
commit 32859713d4
  1. 1
      Cargo.toml
  2. 4
      src/bin/swayrd.rs
  3. 30
      src/client.rs
  4. 69
      src/con.rs
  5. 179
      src/demon.rs
  6. 244
      src/ipc.rs

@ -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"

@ -11,12 +11,12 @@ use swayr::demon;
use swayr::ipc;
fn main() {
let con_props: Arc<RwLock<HashMap<ipc::Id, ipc::ConProps>>> =
let con_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>> =
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);

@ -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) =

@ -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<ipc::ConProps>,
node: &'a r::Node,
workspace: &'a r::Node,
con_props: Option<ipc::ExtraProps>,
}
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<ipc::Id, ipc::ConProps>,
root: &r::Node,
mut con_props: HashMap<i64, ipc::ExtraProps>,
) -> Vec<Window> {
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<ipc::Id, ipc::ConProps>,
root: &r::Node,
mut con_props: HashMap<i64, ipc::ExtraProps>,
) -> Vec<Workspace> {
let mut v = vec![];
for workspace in root.workspaces() {
@ -173,8 +149,7 @@ fn build_workspaces(
v
}
fn get_con_props() -> Result<HashMap<ipc::Id, ipc::ConProps>, serde_json::Error>
{
fn get_con_props() -> Result<HashMap<i64, ipc::ExtraProps>, 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<HashMap<ipc::Id, ipc::ConProps>, serde_json::Error>
}
/// Gets all application windows of the tree.
pub fn get_windows(root: &ipc::Node, sort: bool) -> Vec<Window> {
pub fn get_windows(root: &r::Node, sort: bool) -> Vec<Window> {
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<Window> {
/// Gets all application windows of the tree.
pub fn get_workspaces(
root: &ipc::Node,
root: &r::Node,
include_scratchpad: bool,
) -> Vec<Workspace> {
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<ipc::ConProps>,
node: &'a r::Node,
con_props: Option<ipc::ExtraProps>,
pub windows: Vec<Window<'a>>,
}
@ -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
}

@ -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<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
use swayipc as s;
use swayipc::reply as r;
pub fn monitor_sway_events(
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) {
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::<ipc::ConEvent>() {
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<r::WindowEvent>,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) -> 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<r::WorkspaceEvent>,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) -> 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<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
id: i64,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) {
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<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
fn remove_extra_props(
id: i64,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) {
con_props.write().unwrap().remove(&id);
}
fn handle_con_event(
ev: ipc::ConEvent,
con_props: Arc<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
) {
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<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) {
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<RwLock<HashMap<ipc::Id, ipc::ConProps>>>,
extra_props: Arc<RwLock<HashMap<i64, ipc::ExtraProps>>>,
) {
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);
}

@ -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<String>,
pub instance: Option<String>,
pub title: Option<String>,
//pub window_type: Option<WindowType>
//pub transient_for: DONTKNOW,
}
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
pub struct Node {
pub id: Id,
pub name: Option<String>,
pub rect: Rect,
pub focused: bool,
pub focus: Vec<Id>,
pub border: Border,
pub current_border_width: Dim,
pub layout: Layout,
pub orientation: Orientation,
pub percent: Option<f64>,
pub window_rect: Rect,
pub deco_rect: Rect,
pub geometry: Rect,
pub window: Option<Id>,
pub urgent: bool,
pub marks: Vec<String>,
pub fullscreen_mode: u8, // TODO: actually, it's 0 or 1, i.e., a bool
pub nodes: Vec<Node>,
pub floating_nodes: Vec<Node>,
pub sticky: bool,
pub r#type: NodeType,
pub app_id: Option<String>,
pub visible: Option<bool>,
pub max_render_time: Option<i32>,
pub pid: Option<Pid>,
pub shell: Option<ShellType>,
pub window_properties: Option<WindowProperties>,
}
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<Self::Item> {
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<Node>,
},
WorkspaceEvent {
change: WorkspaceEventType,
// On a reload event, current and old are both null.
current: Option<Box<Node>>,
// If the old was empty, it'll be killed => "old": null.
old: Option<Box<Node>>,
},
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,
}

Loading…
Cancel
Save