├── .gitignore ├── types ├── src │ ├── utils │ │ ├── mod.rs │ │ ├── event.rs │ │ ├── serde.rs │ │ └── node.rs │ ├── lib.rs │ ├── error │ │ ├── command_outcome.rs │ │ ├── command_type.rs │ │ ├── mod.rs │ │ └── event.rs │ ├── event.rs │ ├── command.rs │ └── reply.rs ├── Cargo.toml └── README.md ├── examples ├── command_loop │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── event_printer │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── hovered_window │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── hovered_window_futures_lite │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── command_loop_async_std │ ├── Cargo.toml │ └── src │ │ └── main.rs └── event_printer_tokio │ ├── Cargo.toml │ └── src │ └── main.rs ├── async ├── src │ ├── lib.rs │ ├── common.rs │ ├── event.rs │ ├── socket.rs │ ├── connection.rs │ └── tests.rs ├── Cargo.toml └── README.md ├── blocking ├── src │ ├── lib.rs │ ├── event.rs │ ├── socket.rs │ ├── common.rs │ ├── connection.rs │ └── tests.rs ├── Cargo.toml └── README.md ├── Cargo.toml ├── LICENSE.md ├── .github └── workflows │ └── rust.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /types/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod event; 2 | mod node; 3 | pub mod serde; 4 | -------------------------------------------------------------------------------- /examples/command_loop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "command_loop" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc = { path = "../../blocking" } 9 | -------------------------------------------------------------------------------- /examples/event_printer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "event_printer" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc = { path = "../../blocking" } -------------------------------------------------------------------------------- /examples/hovered_window/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hovered_window" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc = { path = "../../blocking" } -------------------------------------------------------------------------------- /examples/hovered_window_futures_lite/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hovered_window_futures_lite" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc-async = { path = "../../async" } 9 | futures-lite = "1.12.0" -------------------------------------------------------------------------------- /async/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![deny(rust_2018_idioms)] 3 | 4 | mod common; 5 | mod connection; 6 | mod event; 7 | mod socket; 8 | #[cfg(test)] 9 | mod tests; 10 | 11 | pub use connection::Connection; 12 | pub use event::EventStream; 13 | pub use swayipc_types::*; 14 | -------------------------------------------------------------------------------- /blocking/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![deny(rust_2018_idioms)] 3 | 4 | mod common; 5 | mod connection; 6 | mod event; 7 | mod socket; 8 | #[cfg(test)] 9 | mod tests; 10 | 11 | pub use connection::Connection; 12 | pub use event::EventStream; 13 | pub use swayipc_types::*; 14 | -------------------------------------------------------------------------------- /examples/command_loop_async_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "command_loop_async_std" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc-async = { path = "../../async" } 9 | async-std = { version = "1.10.0", features = ["attributes"]} 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "types", 5 | "blocking", 6 | "async", 7 | "examples/command_loop", 8 | "examples/command_loop_async_std", 9 | "examples/event_printer", 10 | "examples/event_printer_tokio", 11 | "examples/hovered_window", 12 | "examples/hovered_window_futures_lite", 13 | ] 14 | -------------------------------------------------------------------------------- /examples/event_printer_tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "event_printer_tokio" 3 | version = "1.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | swayipc-async = { path = "../../async" } 9 | tokio = { version = "1.15.0", features = ["rt-multi-thread", "macros"] } 10 | futures-util = "0.3.19" 11 | -------------------------------------------------------------------------------- /types/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![deny(rust_2018_idioms)] 3 | 4 | mod command; 5 | #[cfg(feature = "error")] 6 | mod error; 7 | mod event; 8 | mod reply; 9 | mod utils; 10 | 11 | pub use command::CommandType; 12 | #[cfg(feature = "error")] 13 | pub use error::{Error, Fallible}; 14 | pub use event::EventType; 15 | pub use reply::*; 16 | 17 | pub const MAGIC: [u8; 6] = [105, 51, 45, 105, 112, 99]; 18 | -------------------------------------------------------------------------------- /examples/hovered_window/src/main.rs: -------------------------------------------------------------------------------- 1 | use swayipc::{Connection, Event, EventType, Fallible}; 2 | 3 | fn main() -> Fallible<()> { 4 | for event in Connection::new()?.subscribe([EventType::Window])? { 5 | match event? { 6 | Event::Window(w) => println!( 7 | "{}", 8 | w.container.name.unwrap_or_else(|| "unnamed".to_owned()) 9 | ), 10 | _ => unreachable!(), 11 | } 12 | } 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /types/src/error/command_outcome.rs: -------------------------------------------------------------------------------- 1 | use crate::{CommandOutcome, Error, Fallible}; 2 | 3 | impl CommandOutcome { 4 | pub fn decode(command_outcome: CommandOutcome) -> Fallible<()> { 5 | if let Some(error) = command_outcome.error { 6 | Err(if error.parse_error { 7 | Error::CommandParse(error.message) 8 | } else { 9 | Error::CommandFailed(error.message) 10 | }) 11 | } else { 12 | Ok(()) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /types/src/error/command_type.rs: -------------------------------------------------------------------------------- 1 | use crate::{CommandType, Error::InvalidCommandType, Fallible}; 2 | use serde::de::DeserializeOwned as Deserialize; 3 | 4 | impl CommandType { 5 | pub fn decode(self, (payload_type, payload): (u32, Vec)) -> Fallible { 6 | let command_type = u32::from(self); 7 | if payload_type != command_type { 8 | return Err(InvalidCommandType(payload_type, command_type)); 9 | } 10 | Ok(serde_json::from_slice(&payload)?) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /blocking/src/event.rs: -------------------------------------------------------------------------------- 1 | use super::common::receive_from_stream; 2 | use crate::{Event, Fallible}; 3 | use std::os::unix::net::UnixStream; 4 | 5 | pub struct EventStream(UnixStream); 6 | 7 | impl EventStream { 8 | pub(super) fn new(stream: UnixStream) -> Self { 9 | Self(stream) 10 | } 11 | } 12 | 13 | impl Iterator for EventStream { 14 | type Item = Fallible; 15 | 16 | fn next(&mut self) -> Option { 17 | Some(receive_from_stream(&mut self.0).and_then(Event::decode)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /blocking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swayipc" 3 | version = "4.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2024" 6 | description = "A library for controlling sway through its IPC interface" 7 | license = "MIT" 8 | repository = "https://github.com/jaycefayne/swayipc-rs" 9 | categories = ["network-programming"] 10 | keywords = ["sway", "swaywm", "swayipc", "ipc"] 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | swayipc-types = { version = "2", path = "../types" } 15 | serde = "1" 16 | serde_json = "1" 17 | -------------------------------------------------------------------------------- /examples/event_printer/src/main.rs: -------------------------------------------------------------------------------- 1 | use swayipc::{Connection, EventType, Fallible}; 2 | 3 | fn main() -> Fallible<()> { 4 | let subs = [ 5 | EventType::Workspace, 6 | EventType::Input, 7 | EventType::Tick, 8 | EventType::Shutdown, 9 | EventType::Mode, 10 | EventType::Window, 11 | EventType::BarStateUpdate, 12 | EventType::BarConfigUpdate, 13 | EventType::Binding, 14 | ]; 15 | for event in Connection::new()?.subscribe(subs)? { 16 | println!("{:?}\n", event?) 17 | } 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swayipc-async" 3 | version = "3.0.0" 4 | authors = ["Jayce Fayne "] 5 | edition = "2024" 6 | description = "A library for controlling sway through its IPC interface" 7 | license = "MIT" 8 | repository = "https://github.com/jaycefayne/swayipc-rs" 9 | categories = ["network-programming"] 10 | keywords = ["sway", "swaywm", "swayipc", "ipc", "async"] 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | swayipc-types = { version = "2", path = "../types" } 15 | serde = "1" 16 | serde_json = "1" 17 | async-io = "2" 18 | futures-lite = "2" 19 | async-pidfd = "0.1.5" 20 | -------------------------------------------------------------------------------- /types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swayipc-types" 3 | version = "2.0.1" 4 | authors = ["Jayce Fayne "] 5 | edition = "2024" 6 | description = "A library containing Type defintions from sway's IPC interface" 7 | license = "MIT" 8 | repository = "https://github.com/jaycefayne/swayipc-rs" 9 | categories = ["network-programming"] 10 | keywords = ["sway", "swaywm", "swayipc", "ipc"] 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | serde = { version = "1", features = ["derive"] } 15 | serde_json = "1" 16 | thiserror = { version = "2", optional = true } 17 | 18 | [features] 19 | default = ["error"] 20 | error = ["thiserror"] 21 | -------------------------------------------------------------------------------- /examples/event_printer_tokio/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures_util::stream::StreamExt; 2 | use swayipc_async::{Connection, EventType, Fallible}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Fallible<()> { 6 | let subs = [ 7 | EventType::Workspace, 8 | EventType::Input, 9 | EventType::Tick, 10 | EventType::Shutdown, 11 | EventType::Mode, 12 | EventType::Window, 13 | EventType::BarStateUpdate, 14 | EventType::BarConfigUpdate, 15 | EventType::Binding, 16 | ]; 17 | let mut events = Connection::new().await?.subscribe(subs).await?; 18 | while let Some(event) = events.next().await { 19 | println!("{:?}\n", event?) 20 | } 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /types/src/utils/event.rs: -------------------------------------------------------------------------------- 1 | use crate::{Event, EventType}; 2 | 3 | impl Event { 4 | pub fn event_type(&self) -> EventType { 5 | match self { 6 | Event::Workspace(_) => EventType::Workspace, 7 | Event::Mode(_) => EventType::Mode, 8 | Event::Window(_) => EventType::Window, 9 | Event::BarConfigUpdate(_) => EventType::BarConfigUpdate, 10 | Event::Binding(_) => EventType::Binding, 11 | Event::Shutdown(_) => EventType::Shutdown, 12 | Event::Tick(_) => EventType::Tick, 13 | Event::BarStateUpdate(_) => EventType::BarStateUpdate, 14 | Event::Input(_) => EventType::Input, 15 | Event::Output(_) => EventType::Output, 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/hovered_window_futures_lite/src/main.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::{future::block_on, stream::StreamExt}; 2 | use swayipc_async::{Connection, Event, EventType, Fallible}; 3 | 4 | fn main() -> Fallible<()> { 5 | block_on(async { 6 | let mut events = Connection::new() 7 | .await? 8 | .subscribe([EventType::Window]) 9 | .await?; 10 | while let Some(event) = events.next().await.transpose()? { 11 | match event { 12 | Event::Window(w) => { 13 | println!( 14 | "{}", 15 | w.container.name.unwrap_or_else(|| "unnamed".to_owned()) 16 | ) 17 | } 18 | _ => unreachable!(), 19 | } 20 | } 21 | Ok(()) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /blocking/src/socket.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::io::Read; 3 | use std::path::PathBuf; 4 | use std::process::{Command, Stdio}; 5 | use swayipc_types::{Error, Fallible}; 6 | 7 | pub fn get_socketpath() -> Fallible { 8 | env::var("I3SOCK") 9 | .or_else(|_| env::var("SWAYSOCK")) 10 | .or_else(|_| spawn("i3")) 11 | .or_else(|_| spawn("sway")) 12 | .map_err(|_| Error::SocketNotFound) 13 | .map(PathBuf::from) 14 | } 15 | 16 | fn spawn(wm: &str) -> Fallible { 17 | let mut child = Command::new(wm) 18 | .arg("--get-socketpath") 19 | .stdout(Stdio::piped()) 20 | .spawn()?; 21 | let mut buf = String::new(); 22 | if let Some(mut stdout) = child.stdout.take() { 23 | stdout.read_to_string(&mut buf)?; 24 | buf.pop(); 25 | } 26 | child.wait()?; 27 | Ok(buf) 28 | } 29 | -------------------------------------------------------------------------------- /blocking/src/common.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::os::unix::net::UnixStream; 3 | use swayipc_types::{Error::InvalidMagic, Fallible, MAGIC}; 4 | 5 | pub(super) fn receive_from_stream(stream: &mut UnixStream) -> Fallible<(u32, Vec)> { 6 | let mut header_buf = [0_u8; 14]; 7 | stream.read_exact(&mut header_buf)?; 8 | let magic_data: [u8; 6] = header_buf[..6].try_into().unwrap(); 9 | if magic_data != MAGIC { 10 | return Err(InvalidMagic(magic_data)); 11 | } 12 | let payload_len_buf: [u8; 4] = header_buf[6..10].try_into().unwrap(); 13 | let payload_len = u32::from_ne_bytes(payload_len_buf); 14 | let reply_type_buf: [u8; 4] = header_buf[10..14].try_into().unwrap(); 15 | let reply_type = u32::from_ne_bytes(reply_type_buf); 16 | let mut reply_payload = vec![0_u8; payload_len as usize]; 17 | stream.read_exact(&mut reply_payload)?; 18 | Ok((reply_type, reply_payload)) 19 | } 20 | -------------------------------------------------------------------------------- /examples/command_loop/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdin, stdout, Write}; 2 | use swayipc::{Connection, Fallible}; 3 | 4 | fn main() -> Fallible<()> { 5 | println!("Executes sway commands in a loop. Enter \"q\" at any time to quit."); 6 | let mut connection = Connection::new()?; 7 | let stdin = stdin(); 8 | let mut stdout = stdout(); 9 | loop { 10 | print!(">>> "); 11 | stdout.flush()?; 12 | let mut command_text = String::new(); 13 | stdin.read_line(&mut command_text)?; 14 | command_text.pop(); // throw away the \n 15 | if command_text == "q" { 16 | break; 17 | } 18 | for outcome in connection.run_command(&command_text)? { 19 | if let Err(error) = outcome { 20 | println!("failure '{}'", error); 21 | } else { 22 | println!("success"); 23 | } 24 | } 25 | } 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /async/src/common.rs: -------------------------------------------------------------------------------- 1 | use async_io::Async; 2 | use futures_lite::AsyncReadExt; 3 | use std::os::unix::net::UnixStream; 4 | use swayipc_types::{Error::InvalidMagic, Fallible, MAGIC}; 5 | 6 | pub(super) async fn receive_from_stream( 7 | stream: &mut Async, 8 | ) -> Fallible<(u32, Vec)> { 9 | let mut header_buf = [0_u8; 14]; 10 | stream.read_exact(&mut header_buf).await?; 11 | let magic_data: [u8; 6] = header_buf[..6].try_into().unwrap(); 12 | if magic_data != MAGIC { 13 | return Err(InvalidMagic(magic_data)); 14 | } 15 | let payload_len_buf: [u8; 4] = header_buf[6..10].try_into().unwrap(); 16 | let payload_len = u32::from_ne_bytes(payload_len_buf); 17 | let reply_type_buf: [u8; 4] = header_buf[10..14].try_into().unwrap(); 18 | let reply_type = u32::from_ne_bytes(reply_type_buf); 19 | let mut reply_payload = vec![0_u8; payload_len as usize]; 20 | stream.read_exact(&mut reply_payload).await?; 21 | Ok((reply_type, reply_payload)) 22 | } 23 | -------------------------------------------------------------------------------- /examples/command_loop_async_std/src/main.rs: -------------------------------------------------------------------------------- 1 | use async_std::io::{prelude::WriteExt, stdin, stdout}; 2 | use swayipc_async::{Connection, Fallible}; 3 | 4 | #[async_std::main] 5 | async fn main() -> Fallible<()> { 6 | println!("Executes sway commands in a loop. Enter \"q\" at any time to quit."); 7 | let mut connection = Connection::new().await?; 8 | let stdin = stdin(); 9 | let mut stdout = stdout(); 10 | loop { 11 | print!(">>> "); 12 | stdout.flush().await?; 13 | let mut command_text = String::new(); 14 | stdin.read_line(&mut command_text).await?; 15 | command_text.pop(); // throw away the \n 16 | if command_text == "q" { 17 | break; 18 | } 19 | for outcome in connection.run_command(&command_text).await? { 20 | if let Err(error) = outcome { 21 | println!("failure '{}'", error); 22 | } else { 23 | println!("success"); 24 | } 25 | } 26 | } 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /async/src/event.rs: -------------------------------------------------------------------------------- 1 | use super::common::receive_from_stream; 2 | use crate::{Event, Fallible}; 3 | use async_io::Async; 4 | use futures_lite::future::Boxed; 5 | use futures_lite::ready; 6 | use futures_lite::stream::Stream; 7 | use std::os::unix::net::UnixStream; 8 | use std::pin::Pin; 9 | use std::task::{Context, Poll}; 10 | 11 | pub struct EventStream(Boxed<(Async, Fallible)>); 12 | 13 | async fn receive(mut stream: Async) -> (Async, Fallible) { 14 | let data = receive_from_stream(&mut stream).await; 15 | (stream, data.and_then(Event::decode)) 16 | } 17 | 18 | impl EventStream { 19 | pub(super) fn new(stream: Async) -> Self { 20 | Self(Box::pin(receive(stream))) 21 | } 22 | } 23 | 24 | impl Stream for EventStream { 25 | type Item = Fallible; 26 | 27 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 28 | let (stream, item) = ready!(self.0.as_mut().poll(cx)); 29 | self.0 = Box::pin(receive(stream)); 30 | Poll::Ready(Some(item)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Jayce Fayne 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /types/src/utils/serde.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use serde::de::{Deserializer, SeqAccess, Visitor}; 3 | use std::fmt; 4 | use std::marker::PhantomData; 5 | 6 | struct SkipNullValues(PhantomData); 7 | 8 | impl<'de, T> Visitor<'de> for SkipNullValues 9 | where 10 | T: Deserialize<'de>, 11 | { 12 | type Value = Vec; 13 | 14 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.write_str("a vec with optional elements") 16 | } 17 | 18 | fn visit_seq(self, mut seq: A) -> Result 19 | where 20 | A: SeqAccess<'de>, 21 | { 22 | let mut vec = Vec::new(); 23 | while let Some(opt) = seq.next_element::>()? { 24 | if let Some(value) = opt { 25 | vec.push(value); 26 | } 27 | } 28 | Ok(vec) 29 | } 30 | } 31 | 32 | pub fn skip_null_values<'de, D, T>(deserializer: D) -> Result, D::Error> 33 | where 34 | D: Deserializer<'de>, 35 | T: Deserialize<'de>, 36 | { 37 | deserializer.deserialize_seq(SkipNullValues(std::marker::PhantomData)) 38 | } 39 | -------------------------------------------------------------------------------- /types/src/error/mod.rs: -------------------------------------------------------------------------------- 1 | mod command_outcome; 2 | mod command_type; 3 | mod event; 4 | 5 | use thiserror::Error as ThisError; 6 | 7 | pub type Fallible = Result; 8 | 9 | #[non_exhaustive] 10 | #[derive(Debug, ThisError)] 11 | pub enum Error { 12 | #[error(transparent)] 13 | Io(#[from] std::io::Error), 14 | #[error(transparent)] 15 | SerdeJson(#[from] serde_json::Error), 16 | #[error("unexpected magic string, expected 'i3-ipc' but got '{}'", String::from_utf8_lossy(.0))] 17 | InvalidMagic([u8; 6]), 18 | #[error("did receive a reply with type '{0}' but send command with type '{1}'")] 19 | InvalidCommandType(u32, u32), 20 | #[error("received unimplemented event '{}' with type '{}'", String::from_utf8_lossy(.1), .0)] 21 | UnimplementedEvent(u32, Vec), 22 | #[error("failed to subscribe to events {0}")] 23 | SubscriptionFailed(String), 24 | #[error("command failed with '{0}'")] 25 | CommandFailed(String), 26 | #[error("command could not be parsed '{0}'")] 27 | CommandParse(String), 28 | #[error("could not find the socket for neither i3 nor sway")] 29 | SocketNotFound, 30 | } 31 | -------------------------------------------------------------------------------- /async/src/socket.rs: -------------------------------------------------------------------------------- 1 | use async_io::Async; 2 | use async_pidfd::AsyncPidFd; 3 | use futures_lite::AsyncReadExt; 4 | use std::env; 5 | use std::path::PathBuf; 6 | use std::process::{Command, Stdio}; 7 | use swayipc_types::{Error, Fallible}; 8 | 9 | pub async fn get_socketpath() -> Fallible { 10 | if let Ok(socketpath) = env::var("I3SOCK") { 11 | Ok(socketpath) 12 | } else if let Ok(socketpath) = env::var("SWAYSOCK") { 13 | Ok(socketpath) 14 | } else if let Ok(socketpath) = spawn("i3").await { 15 | Ok(socketpath) 16 | } else if let Ok(socketpath) = spawn("sway").await { 17 | Ok(socketpath) 18 | } else { 19 | Err(Error::SocketNotFound) 20 | } 21 | .map(PathBuf::from) 22 | } 23 | 24 | async fn spawn(wm: &str) -> Fallible { 25 | let mut child = Command::new(wm) 26 | .arg("--get-socketpath") 27 | .stdout(Stdio::piped()) 28 | .spawn()?; 29 | let mut buf = String::new(); 30 | if let Some(stdout) = child.stdout.take() { 31 | Async::new(stdout)?.read_to_string(&mut buf).await?; 32 | buf.pop(); 33 | } 34 | AsyncPidFd::from_pid(child.id() as i32)?.wait().await?; 35 | Ok(buf) 36 | } 37 | -------------------------------------------------------------------------------- /types/src/event.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[non_exhaustive] 4 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum EventType { 7 | /// Sent whenever an event involving a workspace occurs such as 8 | /// initialization of a new workspace or a different workspace gains focus. 9 | Workspace, 10 | /// Sent whenever an output is added, removed, or its configuration is changed. 11 | Output, 12 | /// Sent whenever the binding mode changes. 13 | Mode, 14 | /// Sent whenever an event involving a view occurs such as being reparented, 15 | /// focused, or closed. 16 | Window, 17 | /// Sent whenever a bar config changes. 18 | #[serde(rename = "barconfig_update")] 19 | BarConfigUpdate, 20 | /// Sent when a configured binding is executed. 21 | Binding, 22 | /// Sent when the ipc shuts down because sway is exiting. 23 | Shutdown, 24 | /// Sent when an ipc client sends a SEND_TICK message. 25 | Tick, 26 | /// Sent when the visibility of a bar should change due to a modifier. 27 | BarStateUpdate, 28 | /// Sent when something related to input devices changes. 29 | Input, 30 | } 31 | -------------------------------------------------------------------------------- /types/src/error/event.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Error::UnimplementedEvent, 3 | Event::{self, *}, 4 | Fallible, 5 | }; 6 | 7 | impl Event { 8 | pub fn decode((payload_type, payload): (u32, Vec)) -> Fallible { 9 | // strip the highest order bit indicating it's an event 10 | // since we dont convert to hex we also dont match on the (hex) values written in the sway-ipc docs! 11 | let event_type = (payload_type << 1) >> 1; 12 | Ok(match event_type { 13 | 0 => Workspace(serde_json::from_slice(&payload)?), 14 | 1 => Output(serde_json::from_slice(&payload)?), 15 | 2 => Mode(serde_json::from_slice(&payload)?), 16 | 3 => Window(serde_json::from_slice(&payload)?), 17 | 4 => BarConfigUpdate(serde_json::from_slice(&payload)?), 18 | 5 => Binding(serde_json::from_slice(&payload)?), 19 | 6 => Shutdown(serde_json::from_slice(&payload)?), 20 | 7 => Tick(serde_json::from_slice(&payload)?), 21 | 20 => BarStateUpdate(serde_json::from_slice(&payload)?), 22 | 21 => Input(serde_json::from_slice(&payload)?), 23 | _ => return Err(UnimplementedEvent(event_type, payload)), 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # swayipc-types   [![Action Badge]][actions] [![Version Badge]][crates.io] [![License Badge]][license] [![Docs Badge]][docs] 2 | 3 | [Version Badge]: https://img.shields.io/crates/v/swayipc-types.svg 4 | [crates.io]: https://crates.io/crates/swayipc-types 5 | [Action Badge]: https://github.com/JayceFayne/swayipc-rs/workflows/Rust/badge.svg 6 | [actions]: https://github.com/JayceFayne/swayipc-rs/actions 7 | [License Badge]: https://img.shields.io/crates/l/swayipc-types.svg 8 | [license]: https://github.com/JayceFayne/swayipc-rs/blob/master/LICENSE.md 9 | [Docs Badge]: https://docs.rs/swayipc-types/badge.svg 10 | [docs]: https://docs.rs/swayipc-types 11 | 12 | A Rust library containing types used to control swaywm through its [IPC interface](https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd). 13 | 14 | ## i3 compatibility 15 | 16 | [i3](https://github.com/i3/i3) compatibility is kept if possible even though this library primarily targets sway. 17 | 18 | ## Versioning 19 | 20 | This library targets the latest stable release of [sway](https://github.com/swaywm/sway). 21 | 22 | ## Contributing 23 | 24 | If you find any errors in swayipc or just want to add a new feature feel free to [submit a PR](https://github.com/jaycefayne/swayipc-rs/pulls). 25 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Build swayipc types 13 | run: cargo build --release --verbose --package swayipc-types 14 | - name: Build blocking swayipc 15 | run: cargo build --release --verbose --package swayipc 16 | - name: Build async swayipc 17 | run: cargo build --release --verbose --package swayipc-async 18 | - name: Build command_loop example 19 | run: cargo build --release --verbose --package command_loop 20 | - name: Build command_loop_async_std example 21 | run: cargo build --release --verbose --package command_loop_async_std 22 | - name: Build event_printer example 23 | run: cargo build --release --verbose --package event_printer 24 | - name: Build event_printer_tokio example 25 | run: cargo build --release --verbose --package event_printer_tokio 26 | - name: Build hovered_window example 27 | run: cargo build --release --verbose --package hovered_window 28 | - name: Build hovered_window_futures_lite example 29 | run: cargo build --release --verbose --package hovered_window_futures_lite 30 | -------------------------------------------------------------------------------- /blocking/README.md: -------------------------------------------------------------------------------- 1 | # swayipc   [![Action Badge]][actions] [![Version Badge]][crates.io] [![License Badge]][license] [![Docs Badge]][docs] 2 | 3 | [Version Badge]: https://img.shields.io/crates/v/swayipc.svg 4 | [crates.io]: https://crates.io/crates/swayipc 5 | [Action Badge]: https://github.com/JayceFayne/swayipc-rs/workflows/Rust/badge.svg 6 | [actions]: https://github.com/JayceFayne/swayipc-rs/actions 7 | [License Badge]: https://img.shields.io/crates/l/swayipc.svg 8 | [license]: https://github.com/JayceFayne/swayipc-rs/blob/master/LICENSE.md 9 | [Docs Badge]: https://docs.rs/swayipc/badge.svg 10 | [docs]: https://docs.rs/swayipc 11 | 12 | A Rust library for controlling swaywm through its [IPC interface](https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd). 13 | 14 | ## Usage 15 | 16 | Examples of how to use the library can be found [here](../examples). 17 | 18 | ## i3 compatibility 19 | 20 | [i3](https://github.com/i3/i3) compatibility is kept if possible even though this library primarily targets sway. 21 | 22 | ## Versioning 23 | 24 | This library targets the latest stable release of [sway](https://github.com/swaywm/sway). 25 | 26 | ## Contributing 27 | 28 | If you find any errors in swayipc or just want to add a new feature feel free to [submit a PR](https://github.com/jaycefayne/swayipc-rs/pulls). 29 | -------------------------------------------------------------------------------- /async/README.md: -------------------------------------------------------------------------------- 1 | # swayipc-async   [![Action Badge]][actions] [![Version Badge]][crates.io] [![License Badge]][license] [![Docs Badge]][docs] 2 | 3 | [Version Badge]: https://img.shields.io/crates/v/swayipc-async.svg 4 | [crates.io]: https://crates.io/crates/swayipc-async 5 | [Action Badge]: https://github.com/JayceFayne/swayipc-rs/workflows/Rust/badge.svg 6 | [actions]: https://github.com/JayceFayne/swayipc-rs/actions 7 | [License Badge]: https://img.shields.io/crates/l/swayipc-async.svg 8 | [license]: https://github.com/JayceFayne/swayipc-rs/blob/master/LICENSE.md 9 | [Docs Badge]: https://docs.rs/swayipc-async/badge.svg 10 | [docs]: https://docs.rs/swayipc-async 11 | 12 | A Rust library for controlling swaywm through its [IPC interface](https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd). This library is executor agnostic and can be used with any async executor! 13 | 14 | ## Usage 15 | 16 | Examples of how to use the library can be found [here](../examples). 17 | 18 | ## i3 compatibility 19 | 20 | [i3](https://github.com/i3/i3) compatibility is kept if possible even though this library primarily targets sway. 21 | 22 | ## Versioning 23 | 24 | This library targets the latest stable release of [sway](https://github.com/swaywm/sway). 25 | 26 | ## Contributing 27 | 28 | If you find any errors in swayipc or just want to add a new feature feel free to [submit a PR](https://github.com/jaycefayne/swayipc-rs/pulls). 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swayipc   [![Action Badge]][actions] [![Version Badge]][crates.io] [![License Badge]][license] 2 | 3 | [Version Badge]: https://img.shields.io/crates/v/swayipc.svg 4 | [crates.io]: https://crates.io/crates/swayipc 5 | [Action Badge]: https://github.com/JayceFayne/swayipc-rs/workflows/Rust/badge.svg 6 | [actions]: https://github.com/JayceFayne/swayipc-rs/actions 7 | [License Badge]: https://img.shields.io/crates/l/swayipc.svg 8 | [license]: https://github.com/JayceFayne/swayipc-rs/blob/master/LICENSE.md 9 | 10 | A Rust library for controlling swaywm through its [IPC interface](https://github.com/swaywm/sway/blob/master/sway/sway-ipc.7.scd). 11 | This repository contains [swayipc](blocking) a blocking way to communicate with sway and [swayipc-async](async) for asynchronus communication. 12 | 13 | ## Usage 14 | 15 | Examples of how to use the library can be found [here](examples). 16 | 17 | ## i3 compatibility 18 | 19 | [i3](https://github.com/i3/i3) compatibility is kept if possible even though this library primarily targets sway. 20 | 21 | ## Versioning 22 | 23 | This library targets the latest stable release of [sway](https://github.com/swaywm/sway). 24 | 25 | ## Contributing 26 | 27 | If you find any errors in swayipc-rs or just want to add a new feature feel free to [submit a PR](https://github.com/jaycefayne/swayipc-rs/pulls). 28 | 29 | ## Credits 30 | 31 | - [Michael Stapelberg](https://github.com/stapelberg) for his awesome work implementing the i3ipc protocol in [go](https://github.com/i3/go-i3). 32 | - [Trevor Merrifield](https://github.com/tmerr) for his awesome work implementing the i3ipc protocol in [rust](https://github.com/tmerr/i3ipc-rs). 33 | - And ofc [Drew DeVault](https://github.com/ddevault) and all the sway contributors for creating sway. 34 | -------------------------------------------------------------------------------- /types/src/command.rs: -------------------------------------------------------------------------------- 1 | #[repr(u32)] 2 | #[non_exhaustive] 3 | #[derive(Debug, PartialEq, Copy, Clone)] 4 | pub enum CommandType { 5 | /// Runs the payload as sway commands. 6 | RunCommand = 0, 7 | /// Get the list of current workspaces. 8 | GetWorkspaces = 1, 9 | /// Subscribe the IPC connection to the events listed in the payload. 10 | Subscribe = 2, 11 | /// Get the list of current outputs. 12 | GetOutputs = 3, 13 | /// Get the node layout tree. 14 | GetTree = 4, 15 | /// Get the names of all the marks currently set. 16 | GetMarks = 5, 17 | /// Get the specified bar config or a list of bar config names. 18 | GetBarConfig = 6, 19 | /// Get the version of sway that owns the IPC socket. 20 | GetVersion = 7, 21 | /// Get the list of binding mode names. 22 | GetBindingModes = 8, 23 | /// Returns the config that was last loaded. 24 | GetConfig = 9, 25 | /// Sends a tick event with the specified payload. 26 | SendTick = 10, 27 | /// Replies failure object for i3 compatibility. 28 | Sync = 11, 29 | /// Request the current binding state, e.g. the currently active binding 30 | /// mode name. 31 | GetBindingState = 12, 32 | /// Get the list of input devices. 33 | GetInputs = 100, 34 | /// Get the list of seats. 35 | GetSeats = 101, 36 | } 37 | 38 | impl CommandType { 39 | pub fn encode(self) -> Vec { 40 | crate::MAGIC 41 | .into_iter() 42 | .chain(0_u32.to_ne_bytes()) 43 | .chain(u32::from(self).to_ne_bytes()) 44 | .collect() 45 | } 46 | 47 | pub fn encode_with>(self, payload: T) -> Vec { 48 | let payload = payload.as_ref(); 49 | crate::MAGIC 50 | .into_iter() 51 | .chain((payload.len() as u32).to_ne_bytes()) 52 | .chain(u32::from(self).to_ne_bytes()) 53 | .chain(payload.iter().cloned()) 54 | .collect() 55 | } 56 | } 57 | 58 | impl From for u32 { 59 | fn from(value: CommandType) -> Self { 60 | value as u32 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /blocking/src/connection.rs: -------------------------------------------------------------------------------- 1 | use super::common::receive_from_stream; 2 | use super::socket::get_socketpath; 3 | use crate::{CommandType::*, Error::SubscriptionFailed, *}; 4 | use serde::de::DeserializeOwned as Deserialize; 5 | use std::io::Write; 6 | use std::os::unix::net::UnixStream; 7 | 8 | #[derive(Debug)] 9 | pub struct Connection(UnixStream); 10 | 11 | impl Connection { 12 | /// Creates a new `Connection` to sway-ipc. 13 | pub fn new() -> Fallible { 14 | let socketpath = get_socketpath()?; 15 | let unix_stream = UnixStream::connect(socketpath)?; 16 | Ok(Self(unix_stream)) 17 | } 18 | 19 | fn raw_command(&mut self, command_type: CommandType) -> Fallible { 20 | self.0.write_all(command_type.encode().as_slice())?; 21 | command_type.decode(receive_from_stream(&mut self.0)?) 22 | } 23 | 24 | fn raw_command_with>( 25 | &mut self, 26 | command_type: CommandType, 27 | payload: T, 28 | ) -> Fallible { 29 | self.0 30 | .write_all(command_type.encode_with(payload).as_slice())?; 31 | command_type.decode(receive_from_stream(&mut self.0)?) 32 | } 33 | 34 | /// Runs the payload as sway commands. 35 | pub fn run_command>(&mut self, payload: T) -> Fallible>> { 36 | let outcome: Vec = self.raw_command_with(RunCommand, payload.as_ref())?; 37 | Ok(outcome.into_iter().map(CommandOutcome::decode).collect()) 38 | } 39 | 40 | /// Get the list of current workspaces. 41 | pub fn get_workspaces(&mut self) -> Fallible> { 42 | self.raw_command(GetWorkspaces) 43 | } 44 | 45 | /// Subscribe the IPC connection to the events listed in the payload. 46 | pub fn subscribe>(mut self, events: T) -> Fallible { 47 | let events = serde_json::ser::to_string(events.as_ref())?; 48 | let res: Success = self.raw_command_with(Subscribe, events.as_bytes())?; 49 | if !res.success { 50 | return Err(SubscriptionFailed(events)); 51 | } 52 | Ok(EventStream::new(self.0)) 53 | } 54 | 55 | /// Get the list of current outputs. 56 | pub fn get_outputs(&mut self) -> Fallible> { 57 | self.raw_command(GetOutputs) 58 | } 59 | 60 | /// Get the node layout tree. 61 | pub fn get_tree(&mut self) -> Fallible { 62 | self.raw_command(GetTree) 63 | } 64 | 65 | /// Get the names of all the marks currently set. 66 | pub fn get_marks(&mut self) -> Fallible> { 67 | self.raw_command(GetMarks) 68 | } 69 | 70 | /// Get a list of bar config names. 71 | pub fn get_bar_ids(&mut self) -> Fallible> { 72 | self.raw_command(GetBarConfig) 73 | } 74 | 75 | /// Get the specified bar config. 76 | pub fn get_bar_config>(&mut self, id: T) -> Fallible { 77 | self.raw_command_with(GetBarConfig, id.as_ref()) 78 | } 79 | 80 | /// Get the version of sway that owns the IPC socket. 81 | pub fn get_version(&mut self) -> Fallible { 82 | self.raw_command(GetVersion) 83 | } 84 | 85 | /// Get the list of binding mode names. 86 | pub fn get_binding_modes(&mut self) -> Fallible> { 87 | self.raw_command(GetBindingModes) 88 | } 89 | 90 | /// Returns the config that was last loaded. 91 | pub fn get_config(&mut self) -> Fallible { 92 | self.raw_command(GetConfig) 93 | } 94 | 95 | /// Sends a tick event with the specified payload. 96 | pub fn send_tick>(&mut self, payload: T) -> Fallible { 97 | let res: Success = self.raw_command_with(SendTick, payload.as_ref())?; 98 | Ok(res.success) 99 | } 100 | 101 | /// Replies failure object for i3 compatibility. 102 | pub fn sync(&mut self) -> Fallible { 103 | let res: Success = self.raw_command::(Sync)?; 104 | Ok(res.success) 105 | } 106 | 107 | /// Request the current binding state, e.g. the currently active binding 108 | /// mode name. 109 | pub fn get_binding_state(&mut self) -> Fallible { 110 | let state: BindingState = self.raw_command(GetBindingState)?; 111 | Ok(state.name) 112 | } 113 | 114 | /// Get the list of input devices. 115 | pub fn get_inputs(&mut self) -> Fallible> { 116 | self.raw_command(GetInputs) 117 | } 118 | 119 | /// Get the list of seats. 120 | pub fn get_seats(&mut self) -> Fallible> { 121 | self.raw_command(GetSeats) 122 | } 123 | } 124 | 125 | impl From for Connection { 126 | fn from(unix_stream: UnixStream) -> Self { 127 | Self(unix_stream) 128 | } 129 | } 130 | 131 | impl From for UnixStream { 132 | fn from(connection: Connection) -> Self { 133 | connection.0 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /blocking/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, EventType}; 2 | 3 | #[test] 4 | fn connect() { 5 | Connection::new().unwrap(); 6 | } 7 | 8 | #[test] 9 | fn run_command_nothing() { 10 | let mut connection = Connection::new().unwrap(); 11 | let result = connection.run_command("").unwrap(); 12 | assert!(result.is_empty()); 13 | } 14 | 15 | #[test] 16 | fn run_command_single_success() { 17 | let mut connection = Connection::new().unwrap(); 18 | let result = connection.run_command("exec /bin/true").unwrap(); 19 | assert_eq!(result.len(), 1); 20 | result[0].as_ref().unwrap(); 21 | } 22 | 23 | #[test] 24 | fn run_command_multiple_success() { 25 | let mut connection = Connection::new().unwrap(); 26 | let result = connection 27 | .run_command("exec /bin/true; exec /bin/true") 28 | .unwrap(); 29 | assert_eq!(result.len(), 2); 30 | result[0].as_ref().unwrap(); 31 | result[1].as_ref().unwrap(); 32 | } 33 | 34 | #[test] 35 | fn run_command_fail() { 36 | let mut connection = Connection::new().unwrap(); 37 | let result = connection.run_command("somerandomcommand").unwrap(); 38 | assert_eq!(result.len(), 1); 39 | assert!(result[0].as_ref().is_err()); 40 | } 41 | 42 | #[test] 43 | fn get_workspaces() { 44 | Connection::new().unwrap().get_workspaces().unwrap(); 45 | } 46 | 47 | #[test] 48 | fn get_outputs() { 49 | Connection::new().unwrap().get_outputs().unwrap(); 50 | } 51 | 52 | #[test] 53 | fn get_tree() { 54 | Connection::new().unwrap().get_tree().unwrap(); 55 | } 56 | 57 | #[test] 58 | fn get_marks() { 59 | Connection::new().unwrap().get_marks().unwrap(); 60 | } 61 | 62 | #[test] 63 | fn get_bar_ids() { 64 | Connection::new().unwrap().get_bar_ids().unwrap(); 65 | } 66 | 67 | #[test] 68 | fn get_bar_ids_and_one_config() { 69 | let mut connection = Connection::new().unwrap(); 70 | let ids = connection.get_bar_ids().unwrap(); 71 | connection.get_bar_config(&ids[0]).unwrap(); 72 | } 73 | 74 | #[test] 75 | fn get_version() { 76 | Connection::new().unwrap().get_version().unwrap(); 77 | } 78 | 79 | #[test] 80 | fn get_binding_modes() { 81 | Connection::new().unwrap().get_binding_modes().unwrap(); 82 | } 83 | 84 | #[test] 85 | fn get_config() { 86 | Connection::new().unwrap().get_config().unwrap(); 87 | } 88 | 89 | #[test] 90 | fn send_tick() { 91 | let success = Connection::new().unwrap().send_tick("").unwrap(); 92 | assert!(success); 93 | } 94 | 95 | #[test] 96 | fn sync() { 97 | let success = Connection::new().unwrap().sync().unwrap(); 98 | assert!(!success, "sync should always return false on sway"); 99 | } 100 | 101 | #[test] 102 | fn get_binding_state() { 103 | Connection::new().unwrap().get_binding_state().unwrap(); 104 | } 105 | 106 | #[test] 107 | fn get_inputs() { 108 | Connection::new().unwrap().get_inputs().unwrap(); 109 | } 110 | 111 | #[test] 112 | fn get_seats() { 113 | Connection::new().unwrap().get_seats().unwrap(); 114 | } 115 | 116 | #[test] 117 | fn event_subscribe_all() { 118 | Connection::new() 119 | .unwrap() 120 | .subscribe(&[ 121 | EventType::Workspace, 122 | EventType::Mode, 123 | EventType::Window, 124 | EventType::BarConfigUpdate, 125 | EventType::Binding, 126 | EventType::Shutdown, 127 | EventType::Tick, 128 | EventType::BarStateUpdate, 129 | EventType::Input, 130 | ]) 131 | .unwrap(); 132 | } 133 | 134 | #[test] 135 | fn find_in_tree() { 136 | assert!(Connection::new() 137 | .unwrap() 138 | .get_tree() 139 | .unwrap() 140 | .find_as_ref(|n| n.focused) 141 | .is_some()); 142 | } 143 | 144 | #[test] 145 | fn find_in_tree_comp() { 146 | assert_eq!( 147 | Connection::new() 148 | .unwrap() 149 | .get_tree() 150 | .unwrap() 151 | .find_as_ref(|n| n.focused), 152 | Connection::new() 153 | .unwrap() 154 | .get_tree() 155 | .unwrap() 156 | .find(|n| n.focused) 157 | .as_ref() 158 | ); 159 | } 160 | 161 | #[test] 162 | fn find_focused_as_ref() { 163 | assert!(Connection::new() 164 | .unwrap() 165 | .get_tree() 166 | .unwrap() 167 | .find_focused_as_ref(|n| n.focused) 168 | .is_some()); 169 | } 170 | 171 | #[test] 172 | fn find_focused() { 173 | assert!(Connection::new() 174 | .unwrap() 175 | .get_tree() 176 | .unwrap() 177 | .find_focused(|n| n.focused) 178 | .is_some()); 179 | } 180 | 181 | #[test] 182 | fn find_in_tree_comp_find_focused() { 183 | assert_eq!( 184 | Connection::new() 185 | .unwrap() 186 | .get_tree() 187 | .unwrap() 188 | .find_focused(|n| n.focused), 189 | Connection::new() 190 | .unwrap() 191 | .get_tree() 192 | .unwrap() 193 | .find(|n| n.focused) 194 | ); 195 | } 196 | -------------------------------------------------------------------------------- /async/src/connection.rs: -------------------------------------------------------------------------------- 1 | use super::common::receive_from_stream; 2 | use super::socket::get_socketpath; 3 | use crate::{CommandType::*, Error::SubscriptionFailed, *}; 4 | use async_io::{Async, Timer}; 5 | use futures_lite::AsyncWriteExt; 6 | use serde::de::DeserializeOwned as Deserialize; 7 | use std::io::ErrorKind::NotConnected; 8 | use std::os::unix::net::UnixStream; 9 | use std::time::Duration; 10 | 11 | const MAX_CONNECT_RETRIES: u8 = u8::MAX; 12 | 13 | #[derive(Debug)] 14 | pub struct Connection(Async); 15 | 16 | impl Connection { 17 | /// Creates a new async `Connection` to sway-ipc. 18 | pub async fn new() -> Fallible { 19 | let socketpath = get_socketpath().await?; 20 | let mut retries = 0; 21 | loop { 22 | let connection = Async::::connect(socketpath.as_path()) 23 | .await 24 | .map(Self); 25 | if matches!(connection.as_ref().map_err(|e| e.kind()), Err(NotConnected)) 26 | && retries != MAX_CONNECT_RETRIES 27 | { 28 | Timer::after(Duration::from_millis(100)).await; 29 | } else { 30 | return Ok(connection?); 31 | } 32 | retries += 1; 33 | } 34 | } 35 | 36 | async fn raw_command(&mut self, command_type: CommandType) -> Fallible { 37 | self.0.write_all(command_type.encode().as_slice()).await?; 38 | command_type.decode(receive_from_stream(&mut self.0).await?) 39 | } 40 | 41 | async fn raw_command_with>( 42 | &mut self, 43 | command_type: CommandType, 44 | payload: T, 45 | ) -> Fallible { 46 | self.0 47 | .write_all(command_type.encode_with(payload).as_slice()) 48 | .await?; 49 | command_type.decode(receive_from_stream(&mut self.0).await?) 50 | } 51 | 52 | /// Runs the payload as sway commands. 53 | pub async fn run_command>(&mut self, payload: T) -> Fallible>> { 54 | let outcome: Vec = 55 | self.raw_command_with(RunCommand, payload.as_ref()).await?; 56 | Ok(outcome.into_iter().map(CommandOutcome::decode).collect()) 57 | } 58 | 59 | /// Get the list of current workspaces. 60 | pub async fn get_workspaces(&mut self) -> Fallible> { 61 | self.raw_command(GetWorkspaces).await 62 | } 63 | 64 | /// Subscribe the IPC connection to the events listed in the payload. 65 | pub async fn subscribe>(mut self, events: T) -> Fallible { 66 | let events = serde_json::ser::to_string(events.as_ref())?; 67 | let res: Success = self.raw_command_with(Subscribe, events.as_bytes()).await?; 68 | if !res.success { 69 | return Err(SubscriptionFailed(events)); 70 | } 71 | Ok(EventStream::new(self.0)) 72 | } 73 | 74 | /// Get the list of current outputs. 75 | pub async fn get_outputs(&mut self) -> Fallible> { 76 | self.raw_command(GetOutputs).await 77 | } 78 | 79 | /// Get the node layout tree. 80 | pub async fn get_tree(&mut self) -> Fallible { 81 | self.raw_command(GetTree).await 82 | } 83 | 84 | /// Get the names of all the marks currently set. 85 | pub async fn get_marks(&mut self) -> Fallible> { 86 | self.raw_command(GetMarks).await 87 | } 88 | 89 | /// Get a list of bar config names. 90 | pub async fn get_bar_ids(&mut self) -> Fallible> { 91 | self.raw_command(GetBarConfig).await 92 | } 93 | 94 | /// Get the specified bar config. 95 | pub async fn get_bar_config>(&mut self, id: T) -> Fallible { 96 | self.raw_command_with(GetBarConfig, id.as_ref()).await 97 | } 98 | 99 | /// Get the version of sway that owns the IPC socket. 100 | pub async fn get_version(&mut self) -> Fallible { 101 | self.raw_command(GetVersion).await 102 | } 103 | 104 | /// Get the list of binding mode names. 105 | pub async fn get_binding_modes(&mut self) -> Fallible> { 106 | self.raw_command(GetBindingModes).await 107 | } 108 | 109 | /// Returns the config that was last loaded. 110 | pub async fn get_config(&mut self) -> Fallible { 111 | self.raw_command(GetConfig).await 112 | } 113 | 114 | /// Sends a tick event with the specified payload. 115 | pub async fn send_tick>(&mut self, payload: T) -> Fallible { 116 | let res: Success = self.raw_command_with(SendTick, payload.as_ref()).await?; 117 | Ok(res.success) 118 | } 119 | 120 | /// Replies failure object for i3 compatibility. 121 | pub async fn sync(&mut self) -> Fallible { 122 | let res: Success = self.raw_command(Sync).await?; 123 | Ok(res.success) 124 | } 125 | 126 | /// Request the current binding state, e.g. the currently active binding 127 | /// mode name. 128 | pub async fn get_binding_state(&mut self) -> Fallible { 129 | let state: BindingState = self.raw_command(GetBindingState).await?; 130 | Ok(state.name) 131 | } 132 | 133 | /// Get the list of input devices. 134 | pub async fn get_inputs(&mut self) -> Fallible> { 135 | self.raw_command(GetInputs).await 136 | } 137 | 138 | /// Get the list of seats. 139 | pub async fn get_seats(&mut self) -> Fallible> { 140 | self.raw_command(GetSeats).await 141 | } 142 | } 143 | 144 | impl From> for Connection { 145 | fn from(unix_stream: Async) -> Self { 146 | Self(unix_stream) 147 | } 148 | } 149 | 150 | impl From for Async { 151 | fn from(connection: Connection) -> Self { 152 | connection.0 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /types/src/utils/node.rs: -------------------------------------------------------------------------------- 1 | use crate::reply::Node; 2 | 3 | impl Node { 4 | pub fn find_as_ref(&self, predicate: F) -> Option<&Node> 5 | where 6 | F: Copy + Fn(&Node) -> bool, 7 | { 8 | if predicate(self) { 9 | return Some(self); 10 | } 11 | for node in &self.nodes { 12 | let node = node.find_as_ref(predicate); 13 | if node.is_some() { 14 | return node; 15 | } 16 | } 17 | for node in &self.floating_nodes { 18 | let node = node.find_as_ref(predicate); 19 | if node.is_some() { 20 | return node; 21 | } 22 | } 23 | None 24 | } 25 | 26 | pub fn find(self, predicate: F) -> Option 27 | where 28 | F: Copy + Fn(&Node) -> bool, 29 | { 30 | if predicate(&self) { 31 | return Some(self); 32 | } 33 | let Node { 34 | nodes, 35 | floating_nodes, 36 | .. 37 | } = self; 38 | for node in nodes { 39 | let node = node.find(predicate); 40 | if node.is_some() { 41 | return node; 42 | } 43 | } 44 | for node in floating_nodes { 45 | let node = node.find(predicate); 46 | if node.is_some() { 47 | return node; 48 | } 49 | } 50 | None 51 | } 52 | 53 | pub fn find_focused_as_ref(&self, predicate: F) -> Option<&Node> 54 | where 55 | F: Copy + Fn(&Node) -> bool, 56 | { 57 | if predicate(self) { 58 | return Some(self); 59 | } 60 | if self.focus.is_empty() { 61 | return None; 62 | } 63 | let first = self.focus[0]; 64 | for node in &self.nodes { 65 | if node.id == first { 66 | return node.find_focused_as_ref(predicate); 67 | } 68 | } 69 | for node in &self.floating_nodes { 70 | if node.id == first { 71 | return node.find_focused_as_ref(predicate); 72 | } 73 | } 74 | None 75 | } 76 | 77 | pub fn find_focused(self, predicate: F) -> Option 78 | where 79 | F: Copy + Fn(&Node) -> bool, 80 | { 81 | if predicate(&self) { 82 | return Some(self); 83 | } 84 | let Node { 85 | nodes, 86 | floating_nodes, 87 | focus, 88 | .. 89 | } = self; 90 | if focus.is_empty() { 91 | return None; 92 | } 93 | let first = focus[0]; 94 | for node in nodes { 95 | if node.id == first { 96 | return node.find_focused(predicate); 97 | } 98 | } 99 | for node in floating_nodes { 100 | if node.id == first { 101 | return node.find_focused(predicate); 102 | } 103 | } 104 | None 105 | } 106 | 107 | pub fn iter(&self) -> NodeIterator<'_> { 108 | NodeIterator { queue: vec![self] } 109 | } 110 | } 111 | 112 | pub struct NodeIterator<'a> { 113 | queue: Vec<&'a Node>, 114 | } 115 | 116 | impl<'a> Iterator for NodeIterator<'a> { 117 | type Item = &'a Node; 118 | 119 | fn next(&mut self) -> Option<&'a Node> { 120 | match self.queue.pop() { 121 | None => None, 122 | Some(result) => { 123 | self.queue 124 | .extend(result.nodes.iter().chain(result.floating_nodes.iter())); 125 | Some(result) 126 | } 127 | } 128 | } 129 | } 130 | 131 | #[cfg(test)] 132 | mod tests { 133 | use super::*; 134 | use crate::NodeBorder; 135 | use crate::NodeLayout; 136 | use crate::NodeType; 137 | use crate::Orientation; 138 | use crate::Rect; 139 | 140 | fn rect() -> Rect { 141 | Rect { 142 | x: 0, 143 | y: 0, 144 | width: 10, 145 | height: 10, 146 | } 147 | } 148 | 149 | fn mk_node(name: Option, node_type: NodeType, nodes: Vec) -> Node { 150 | Node { 151 | id: 1, 152 | name, 153 | node_type, 154 | border: NodeBorder::Normal, 155 | current_border_width: 0, 156 | layout: NodeLayout::Tabbed, 157 | percent: None, 158 | rect: rect(), 159 | window_rect: rect(), 160 | deco_rect: rect(), 161 | geometry: rect(), 162 | urgent: false, 163 | focused: false, 164 | focus: Vec::new(), 165 | nodes, 166 | floating_nodes: Vec::new(), 167 | sticky: false, 168 | representation: None, 169 | fullscreen_mode: None, 170 | floating: None, 171 | scratchpad_state: None, 172 | app_id: None, 173 | pid: None, 174 | window: None, 175 | num: None, 176 | window_properties: None, 177 | marks: Vec::new(), 178 | inhibit_idle: None, 179 | idle_inhibitors: None, 180 | shell: None, 181 | visible: None, 182 | output: None, 183 | sandbox_engine: None, 184 | sandbox_app_id: None, 185 | sandbox_instance_id: None, 186 | tag: None, 187 | foreign_toplevel_identifier: None, 188 | orientation: Orientation::None, 189 | } 190 | } 191 | 192 | #[test] 193 | fn returns_the_given_root_node_first() { 194 | let root = mk_node(Some(String::from("root")), NodeType::Root, vec![]); 195 | let mut iterator = root.iter(); 196 | assert_eq!(iterator.next().unwrap().name, Some("root".to_string())); 197 | } 198 | 199 | #[test] 200 | fn returns_children_of_the_given_node() { 201 | let root = mk_node( 202 | Some("root".to_string()), 203 | NodeType::Root, 204 | vec![mk_node(Some("child".to_string()), NodeType::Con, vec![])], 205 | ); 206 | let mut iterator = root.iter(); 207 | iterator.next(); 208 | assert_eq!(iterator.next().unwrap().name, Some("child".to_string())); 209 | } 210 | 211 | #[test] 212 | fn returns_transitive_children_of_the_given_node() { 213 | let root = mk_node( 214 | Some("root".to_string()), 215 | NodeType::Root, 216 | vec![mk_node( 217 | Some("child".to_string()), 218 | NodeType::Con, 219 | vec![mk_node( 220 | Some("grandchild".to_string()), 221 | NodeType::Con, 222 | vec![], 223 | )], 224 | )], 225 | ); 226 | let mut iterator = root.iter(); 227 | iterator.next(); 228 | iterator.next(); 229 | assert_eq!( 230 | iterator.next().unwrap().name, 231 | Some("grandchild".to_string()) 232 | ); 233 | } 234 | 235 | #[test] 236 | fn returns_none_at_the_end() { 237 | let root = mk_node(Some("root".to_string()), NodeType::Root, vec![]); 238 | let mut iterator = root.iter(); 239 | iterator.next(); 240 | assert_eq!(iterator.next(), None); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /async/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, EventType}; 2 | use async_io::block_on; 3 | 4 | #[test] 5 | fn connect() { 6 | block_on(async { 7 | Connection::new().await.unwrap(); 8 | }); 9 | } 10 | 11 | #[test] 12 | fn run_command_nothing() { 13 | block_on(async { 14 | let mut connection = Connection::new().await.unwrap(); 15 | let result = connection.run_command("").await.unwrap(); 16 | assert!(result.is_empty()); 17 | }); 18 | } 19 | 20 | #[test] 21 | fn run_command_single_success() { 22 | block_on(async { 23 | let mut connection = Connection::new().await.unwrap(); 24 | let result = connection.run_command("exec /bin/true").await.unwrap(); 25 | assert_eq!(result.len(), 1); 26 | result[0].as_ref().unwrap(); 27 | }); 28 | } 29 | 30 | #[test] 31 | fn run_command_multiple_success() { 32 | block_on(async { 33 | let mut connection = Connection::new().await.unwrap(); 34 | let result = connection 35 | .run_command("exec /bin/true; exec /bin/true") 36 | .await 37 | .unwrap(); 38 | assert_eq!(result.len(), 2); 39 | result[0].as_ref().unwrap(); 40 | result[1].as_ref().unwrap(); 41 | }); 42 | } 43 | 44 | #[test] 45 | fn run_command_fail() { 46 | block_on(async { 47 | let mut connection = Connection::new().await.unwrap(); 48 | let result = connection.run_command("somerandomcommand").await.unwrap(); 49 | assert_eq!(result.len(), 1); 50 | assert!(result[0].as_ref().is_err()); 51 | }); 52 | } 53 | 54 | #[test] 55 | fn get_workspaces() { 56 | block_on(async { 57 | Connection::new() 58 | .await 59 | .unwrap() 60 | .get_workspaces() 61 | .await 62 | .unwrap(); 63 | }); 64 | } 65 | 66 | #[test] 67 | fn get_outputs() { 68 | block_on(async { 69 | Connection::new() 70 | .await 71 | .unwrap() 72 | .get_outputs() 73 | .await 74 | .unwrap(); 75 | }); 76 | } 77 | 78 | #[test] 79 | fn get_tree() { 80 | block_on(async { 81 | Connection::new().await.unwrap().get_tree().await.unwrap(); 82 | }); 83 | } 84 | 85 | #[test] 86 | fn get_marks() { 87 | block_on(async { 88 | Connection::new().await.unwrap().get_marks().await.unwrap(); 89 | }); 90 | } 91 | 92 | #[test] 93 | fn get_bar_ids() { 94 | block_on(async { 95 | Connection::new() 96 | .await 97 | .unwrap() 98 | .get_bar_ids() 99 | .await 100 | .unwrap(); 101 | }); 102 | } 103 | 104 | #[test] 105 | fn get_bar_ids_and_one_config() { 106 | block_on(async { 107 | let mut connection = Connection::new().await.unwrap(); 108 | let ids = connection.get_bar_ids().await.unwrap(); 109 | connection.get_bar_config(&ids[0]).await.unwrap(); 110 | }); 111 | } 112 | 113 | #[test] 114 | fn get_version() { 115 | block_on(async { 116 | Connection::new() 117 | .await 118 | .unwrap() 119 | .get_version() 120 | .await 121 | .unwrap(); 122 | }); 123 | } 124 | 125 | #[test] 126 | fn get_binding_modes() { 127 | block_on(async { 128 | Connection::new() 129 | .await 130 | .unwrap() 131 | .get_binding_modes() 132 | .await 133 | .unwrap(); 134 | }); 135 | } 136 | 137 | #[test] 138 | fn get_config() { 139 | block_on(async { 140 | Connection::new().await.unwrap().get_config().await.unwrap(); 141 | }); 142 | } 143 | 144 | #[test] 145 | fn send_tick() { 146 | block_on(async { 147 | let success = Connection::new() 148 | .await 149 | .unwrap() 150 | .send_tick("") 151 | .await 152 | .unwrap(); 153 | assert!(success); 154 | }); 155 | } 156 | 157 | #[test] 158 | fn sync() { 159 | block_on(async { 160 | let success = Connection::new().await.unwrap().sync().await.unwrap(); 161 | assert!(!success, "sync should always return false on sway"); 162 | }); 163 | } 164 | 165 | #[test] 166 | fn get_binding_state() { 167 | block_on(async { 168 | Connection::new() 169 | .await 170 | .unwrap() 171 | .get_binding_state() 172 | .await 173 | .unwrap(); 174 | }); 175 | } 176 | 177 | #[test] 178 | fn get_inputs() { 179 | block_on(async { 180 | Connection::new().await.unwrap().get_inputs().await.unwrap(); 181 | }); 182 | } 183 | 184 | #[test] 185 | fn get_seats() { 186 | block_on(async { 187 | Connection::new().await.unwrap().get_seats().await.unwrap(); 188 | }); 189 | } 190 | 191 | #[test] 192 | fn event_subscribe_all() { 193 | block_on(async { 194 | Connection::new() 195 | .await 196 | .unwrap() 197 | .subscribe(&[ 198 | EventType::Workspace, 199 | EventType::Mode, 200 | EventType::Window, 201 | EventType::BarConfigUpdate, 202 | EventType::Binding, 203 | EventType::Shutdown, 204 | EventType::Tick, 205 | EventType::BarStateUpdate, 206 | EventType::Input, 207 | ]) 208 | .await 209 | .unwrap(); 210 | }); 211 | } 212 | 213 | #[test] 214 | fn find_in_tree() { 215 | block_on(async { 216 | assert!(Connection::new() 217 | .await 218 | .unwrap() 219 | .get_tree() 220 | .await 221 | .unwrap() 222 | .find_as_ref(|n| n.focused) 223 | .is_some()); 224 | }); 225 | } 226 | 227 | #[test] 228 | fn find_in_tree_comp() { 229 | block_on(async { 230 | assert_eq!( 231 | Connection::new() 232 | .await 233 | .unwrap() 234 | .get_tree() 235 | .await 236 | .unwrap() 237 | .find_as_ref(|n| n.focused), 238 | Connection::new() 239 | .await 240 | .unwrap() 241 | .get_tree() 242 | .await 243 | .unwrap() 244 | .find(|n| n.focused) 245 | .as_ref() 246 | ); 247 | }); 248 | } 249 | 250 | #[test] 251 | fn find_focused_as_ref() { 252 | block_on(async { 253 | assert!(Connection::new() 254 | .await 255 | .unwrap() 256 | .get_tree() 257 | .await 258 | .unwrap() 259 | .find_focused_as_ref(|n| n.focused) 260 | .is_some()); 261 | }); 262 | } 263 | 264 | #[test] 265 | fn find_focused() { 266 | block_on(async { 267 | assert!(Connection::new() 268 | .await 269 | .unwrap() 270 | .get_tree() 271 | .await 272 | .unwrap() 273 | .find_focused(|n| n.focused) 274 | .is_some()); 275 | }); 276 | } 277 | 278 | #[test] 279 | fn find_in_tree_comp_find_focused() { 280 | block_on(async { 281 | assert_eq!( 282 | Connection::new() 283 | .await 284 | .unwrap() 285 | .get_tree() 286 | .await 287 | .unwrap() 288 | .find_focused(|n| n.focused), 289 | Connection::new() 290 | .await 291 | .unwrap() 292 | .get_tree() 293 | .await 294 | .unwrap() 295 | .find(|n| n.focused) 296 | ); 297 | }); 298 | } 299 | -------------------------------------------------------------------------------- /types/src/reply.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::serde::skip_null_values; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[non_exhaustive] 5 | #[derive(Clone, Debug, Deserialize, Serialize)] 6 | pub struct CommandOutcome { 7 | /// A boolean indicating whether the command was successful. 8 | pub success: bool, 9 | /// An error object if the command failed, and None otherwise. 10 | #[serde(flatten)] 11 | pub error: Option, 12 | } 13 | 14 | #[non_exhaustive] 15 | #[derive(Clone, Debug, Deserialize, Serialize)] 16 | pub struct CommandError { 17 | /// A boolean indicating whether the reason the command failed was because 18 | /// the command was unknown or not able to be parsed. 19 | pub parse_error: bool, 20 | /// A human readable error message. 21 | #[serde(rename = "error")] 22 | pub message: String, 23 | } 24 | 25 | #[non_exhaustive] 26 | #[derive(Clone, Debug, Deserialize, Serialize)] 27 | pub struct Workspace { 28 | pub id: i64, 29 | /// The workspace number or -1 for workspaces that do not start with a 30 | /// number. 31 | pub num: i32, 32 | /// The name of the workspace. 33 | pub name: String, 34 | #[serde(default)] 35 | pub layout: NodeLayout, 36 | /// Whether the workspace is currently visible on any output. 37 | pub visible: bool, 38 | /// Whether the workspace is currently focused by the default seat (seat0). 39 | pub focused: bool, 40 | /// Whether a view on the workspace has the urgent flag set. 41 | pub urgent: bool, 42 | pub representation: Option, 43 | /// The workspace orientation. It can be vertical, horizontal, or none 44 | #[serde(default)] 45 | pub orientation: Orientation, 46 | /// The bounds of the workspace. It consists of x, y, width, and height. 47 | pub rect: Rect, 48 | /// The name of the output that the workspace is on. 49 | pub output: String, 50 | #[serde(default)] 51 | pub focus: Vec, 52 | } 53 | 54 | #[non_exhaustive] 55 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 56 | pub struct Success { 57 | /// A boolean value indicating whether the operation was successful or not. 58 | pub success: bool, 59 | } 60 | 61 | #[non_exhaustive] 62 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 63 | pub struct Mode { 64 | pub width: i32, 65 | pub height: i32, 66 | pub refresh: i32, 67 | } 68 | 69 | #[non_exhaustive] 70 | #[derive(Clone, Debug, Deserialize, Serialize)] 71 | pub struct Output { 72 | pub id: Option, // Sway doesn't give disabled outputs ids 73 | /// The name of the output. On DRM, this is the connector. 74 | pub name: String, 75 | /// The make of the output. 76 | pub make: String, 77 | /// The model of the output. 78 | pub model: String, 79 | /// The output's serial number as a hexa‐ decimal string. 80 | pub serial: String, 81 | /// Whether this output is active/enabled. 82 | #[serde(default)] 83 | pub active: bool, 84 | /// Whether this is a non-desktop output (e.g. VR headset). 85 | #[serde(default)] 86 | pub non_desktop: bool, 87 | /// Whether this output is on/off (via DPMS). 88 | #[serde(default)] 89 | pub dpms: bool, 90 | /// Whether this output is on/off 91 | #[serde(default)] 92 | pub power: bool, 93 | /// For i3 compatibility, this will be false. It does not make sense in 94 | /// Wayland. 95 | pub primary: bool, 96 | /// The scale currently in use on the output or -1 for disabled outputs. 97 | pub scale: Option, 98 | /// The subpixel hinting current in use on the output. This can be rgb, bgr, 99 | /// vrgb, vbgr, or none. 100 | pub subpixel_hinting: Option, 101 | /// The transform currently in use for the output. This can be normal, 90, 102 | /// 180, 270, flipped-90, flipped-180, or flipped-270. 103 | pub transform: Option, 104 | /// Status of adaptive sync 105 | pub adaptive_sync_status: Option, 106 | /// The workspace currently visible on the output or null for disabled 107 | /// outputs. 108 | pub current_workspace: Option, 109 | /// An array of supported mode objects. Each object contains width, height, 110 | /// and refresh. 111 | #[serde(default)] 112 | pub modes: Vec, 113 | /// An object representing the current mode containing width, height, and 114 | /// refresh. 115 | pub current_mode: Option, 116 | /// The bounds for the output consisting of x, y, width, and height. 117 | #[serde(default)] 118 | pub rect: Rect, 119 | #[serde(default)] 120 | pub focus: Vec, 121 | #[serde(default)] 122 | pub focused: bool, 123 | /// Whether HDR is enabled 124 | #[serde(default)] 125 | pub hdr: bool, 126 | } 127 | 128 | #[non_exhaustive] 129 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 130 | pub struct Libinput { 131 | /// Whether events are being sent by the device. It can be enabled, 132 | /// disabled, or disabled_on_external_mouse. 133 | pub send_events: Option, 134 | /// Whether tap to click is enabled. It can be enabled or disabled. 135 | pub tap: Option, 136 | /// The finger to button mapping in use. It can be lmr or lrm. 137 | pub tap_button_mapping: Option, 138 | /// Whether tap-and-drag is enabled. It can be enabled or disabled. 139 | pub tap_drag: Option, 140 | /// Whether drag-lock is enabled. It can be enabled, disabled or enabled_sticky. 141 | pub tap_drag_lock: Option, 142 | /// The pointer-acceleration in use. 143 | pub accel_speed: Option, 144 | /// Whether natural scrolling is enabled. It can be enabled or disabled. 145 | pub natural_scroll: Option, 146 | /// Whether left-handed mode is enabled. It can be enabled or disabled. 147 | pub left_handed: Option, 148 | /// The click method in use. It can be none, button_areas, or clickfinger. 149 | pub click_method: Option, 150 | /// The finger to button mapping in use for clickfinger. 151 | pub click_button_map: Option, 152 | /// Whether middle emulation is enabled. It can be enabled or disabled. 153 | pub middle_emulation: Option, 154 | /// The scroll method in use. It can be none, two_finger, edge, or 155 | /// on_button_down. 156 | pub scroll_method: Option, 157 | /// The scroll button to use when scroll_method is on_button_down. This 158 | /// will be given as an input event code. 159 | pub scroll_button: Option, 160 | /// Whether scroll button lock is enabled. 161 | pub scroll_button_lock: Option, 162 | /// Whether disable-while-typing is enabled. It can be enabled or disabled. 163 | pub dwt: Option, 164 | /// An array of 6 floats representing the calibration matrix for absolute 165 | /// devices such as touchscreens. 166 | pub calibration_matrix: Option<[f32; 6]>, 167 | } 168 | 169 | #[non_exhaustive] 170 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 171 | #[serde(rename_all = "snake_case")] 172 | pub enum SendEvents { 173 | Enabled, 174 | Disabled, 175 | DisabledOnExternalMouse, 176 | } 177 | 178 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 179 | #[serde(rename_all = "lowercase")] 180 | pub enum EnabledOrDisabled { 181 | Enabled, 182 | Disabled, 183 | } 184 | 185 | #[non_exhaustive] 186 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 187 | #[serde(rename_all = "snake_case")] 188 | pub enum ClickMethod { 189 | ButtonAreas, 190 | Clickfinger, 191 | None, 192 | } 193 | 194 | #[non_exhaustive] 195 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 196 | #[serde(rename_all = "snake_case")] 197 | pub enum ScrollMethod { 198 | TwoFinger, 199 | Edge, 200 | OnButtonDown, 201 | None, 202 | } 203 | 204 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 205 | #[serde(rename_all = "lowercase")] 206 | pub enum ButtonMapping { 207 | LMR, 208 | LRM, 209 | } 210 | 211 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 212 | #[serde(rename_all = "snake_case")] 213 | pub enum DragLock { 214 | Enabled, 215 | Disabled, 216 | EnabledSticky, 217 | } 218 | 219 | #[non_exhaustive] 220 | #[derive(Clone, Debug, Deserialize, Serialize)] 221 | pub struct Input { 222 | /// The identifier for the input device. 223 | pub identifier: String, 224 | /// The human readable name for the device. 225 | pub name: String, 226 | /// The device type. Currently this can be keyboard, pointer, touch, 227 | /// tablet_tool, tablet_pad, or switch. 228 | #[serde(rename = "type")] 229 | pub input_type: String, 230 | /// (Only keyboards) The name of the active keyboard layout in use. 231 | pub xkb_active_layout_name: Option, 232 | /// (Only keyboards) A list a layout names configured for the keyboard. 233 | #[serde(default, deserialize_with = "skip_null_values")] 234 | pub xkb_layout_names: Vec, 235 | /// (Only keyboards) The index of the active keyboard layout in use. 236 | pub xkb_active_layout_index: Option, 237 | /// (Only pointers) Multiplier applied on scroll event values. 238 | #[serde(default)] 239 | pub scroll_factor: f64, 240 | /// (Only libinput devices) An object describing the current device 241 | /// settings. See below for more information. 242 | pub libinput: Option, 243 | /// (Only libinput devices) The vendor code for the input device. 244 | pub vendor: Option, 245 | /// (Only libinput devices) The product code for the input device. 246 | pub product: Option, 247 | } 248 | 249 | #[non_exhaustive] 250 | #[derive(Clone, Debug, Deserialize, Serialize)] 251 | pub struct Seat { 252 | /// The unique name for the seat. 253 | pub name: String, 254 | /// The number of capabilities that the seat has. 255 | pub capabilities: i32, 256 | /// The id of the node currently focused by the seat or 0 when the seat is 257 | /// not currently focused by a node (i.e. a surface layer or xwayland 258 | /// unmanaged has focus). 259 | pub focus: i64, 260 | /// An array of input devices that are attached to the seat. Currently, this 261 | /// is an array of objects that are identical to those returned by 262 | /// GET_INPUTS. 263 | #[serde(default)] 264 | pub devices: Vec, 265 | } 266 | 267 | #[non_exhaustive] 268 | #[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)] 269 | pub struct Rect { 270 | pub x: i32, 271 | pub y: i32, 272 | pub width: i32, 273 | pub height: i32, 274 | } 275 | 276 | #[non_exhaustive] 277 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 278 | pub struct WindowProperties { 279 | pub title: Option, 280 | pub instance: Option, 281 | pub class: Option, 282 | pub window_role: Option, 283 | pub window_type: Option, 284 | pub transient_for: Option, 285 | } 286 | 287 | #[non_exhaustive] 288 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 289 | #[serde(rename_all = "lowercase")] 290 | pub enum UserIdleInhibitType { 291 | Focus, 292 | Fullscreen, 293 | Open, 294 | Visible, 295 | None, 296 | } 297 | 298 | #[non_exhaustive] 299 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 300 | #[serde(rename_all = "lowercase")] 301 | pub enum ApplicationIdleInhibitType { 302 | Enabled, 303 | None, 304 | } 305 | 306 | #[non_exhaustive] 307 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 308 | pub struct IdleInhibitors { 309 | pub application: ApplicationIdleInhibitType, 310 | pub user: UserIdleInhibitType, 311 | } 312 | 313 | #[non_exhaustive] 314 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 315 | #[serde(rename_all = "snake_case")] 316 | pub enum NodeType { 317 | Root, 318 | Output, 319 | Workspace, 320 | Con, 321 | FloatingCon, 322 | Dockarea, // i3-specific 323 | } 324 | 325 | #[non_exhaustive] 326 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 327 | #[serde(rename_all = "lowercase")] 328 | pub enum NodeBorder { 329 | Normal, 330 | Pixel, 331 | Csd, 332 | None, 333 | } 334 | 335 | #[non_exhaustive] 336 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, Default)] 337 | #[serde(rename_all = "lowercase")] 338 | pub enum NodeLayout { 339 | SplitH, 340 | SplitV, 341 | Stacked, 342 | Tabbed, 343 | Output, 344 | Dockarea, // i3-specific 345 | #[default] 346 | None, 347 | } 348 | 349 | #[non_exhaustive] 350 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, Default)] 351 | #[serde(rename_all = "lowercase")] 352 | pub enum Orientation { 353 | Vertical, 354 | Horizontal, 355 | #[default] 356 | None, 357 | } 358 | 359 | #[non_exhaustive] 360 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 361 | #[serde(rename_all = "snake_case")] 362 | pub enum Floating { 363 | AutoOn, 364 | AutoOff, 365 | UserOn, 366 | UserOff, 367 | } 368 | 369 | #[non_exhaustive] 370 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 371 | #[serde(rename_all = "lowercase")] 372 | pub enum ScratchpadState { 373 | None, 374 | Fresh, 375 | Changed, 376 | } 377 | 378 | #[non_exhaustive] 379 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 380 | pub struct Node { 381 | /// The internal unique ID for this node. 382 | pub id: i64, 383 | /// The name of the node such as the output name or window title. For the 384 | /// scratchpad, this will be __i3_scratch for compatibility with i3. 385 | pub name: Option, 386 | /// The node type. It can be root, output, workspace, con, or floating_con. 387 | #[serde(rename = "type")] 388 | pub node_type: NodeType, 389 | /// The border style for the node. It can be normal, none, pixel, or csd. 390 | pub border: NodeBorder, 391 | /// Number of pixels used for the border width. 392 | pub current_border_width: i32, 393 | /// The node's layout. It can either be splith, splitv, stacked, tabbed, or 394 | /// output. 395 | pub layout: NodeLayout, 396 | /// The node's orientation. It can be vertical, horizontal, or none 397 | pub orientation: Orientation, 398 | /// The percentage of the node's parent that it takes up or null for the 399 | /// root and other special nodes such as the scratchpad. 400 | pub percent: Option, 401 | /// The absolute geometry of the node. The window decorations are excluded 402 | /// from this, but borders are included. 403 | pub rect: Rect, 404 | /// The geometry of the contents inside the node. The window decorations are 405 | /// excluded from this calculation, but borders are included. 406 | pub window_rect: Rect, 407 | /// The geometry of the decorations for the node relative to the parent 408 | /// node. 409 | pub deco_rect: Rect, 410 | /// The natural geometry of the contents if it were to size itself. 411 | pub geometry: Rect, 412 | /// Whether the node or any of its descendants has the urgent hint set. 413 | /// Note: This may not exist when compiled without xwayland support. 414 | pub urgent: bool, 415 | /// Whether the node is currently focused by the default seat (seat0). 416 | pub focused: bool, 417 | /// Array of child node IDs in the current focus order. 418 | pub focus: Vec, 419 | /// Floating state of container, i3 specific property 420 | pub floating: Option, 421 | /// The tiling children nodes for the node. 422 | #[serde(default)] 423 | pub nodes: Vec, 424 | /// The floating children nodes for the node. 425 | pub floating_nodes: Vec, 426 | /// Whether the node is sticky (shows on all workspaces). 427 | pub sticky: bool, 428 | /// (Only workspaces) A string representation of the layout of the workspace 429 | /// that can be used as an aid in submitting reproduction steps for bug 430 | /// reports. 431 | pub representation: Option, 432 | /// (Only views) The fullscreen mode of the node. 0 means 433 | /// none, 1 means full workspace, and 2 means global fullscreen. 434 | pub fullscreen_mode: Option, 435 | /// (Only views) For an xdg-shell and xwayland view, whether the window is in the scratchpad. 436 | /// Otherwise, null. 437 | pub scratchpad_state: Option, 438 | /// (Only views) For an xdg-shell view, the name of the application, if set. 439 | /// Otherwise, null. 440 | pub app_id: Option, 441 | /// (Only views) The PID of the application that owns the view. 442 | pub pid: Option, 443 | /// (Only xwayland views) The X11 window ID for the xwayland view. 444 | pub window: Option, 445 | pub num: Option, //workspace number if `node_type` == `NodeType::Workspace` 446 | /// (Only xwayland views) An object containing the title, class, instance, 447 | /// window_role, window_type, and transient_for for the view. 448 | pub window_properties: Option, 449 | /// List of marks assigned to the node. 450 | #[serde(default)] 451 | pub marks: Vec, 452 | /// (Only views) Whether the view is inhibiting the idle state. 453 | pub inhibit_idle: Option, 454 | /// (Only views) An object containing the state of the application and user 455 | /// idle inhibitors. application can be enabled or none. user can be 456 | /// focus, fullscreen, open, visible or none. 457 | pub idle_inhibitors: Option, 458 | /// (Only views) The associated sandbox engine. 459 | pub sandbox_engine: Option, 460 | /// Only views) The app ID provided by the associated sandbox engine. 461 | pub sandbox_app_id: Option, 462 | /// (Only views) The instance ID provided by the associated sandbox engine. 463 | pub sandbox_instance_id: Option, 464 | /// (Only windows) For an xdg-shell window, tag of the toplevel, if set. 465 | pub tag: Option, 466 | /// (Only views) The shell of the view, such as xdg_shell or xwayland. 467 | pub shell: Option, 468 | /// (Only views) The ext-foreign-toplevel-list-v1 toplevel identifier of this node. 469 | pub foreign_toplevel_identifier: Option, 470 | /// (Only views) Whether the node is visible. 471 | pub visible: Option, 472 | /// (Only workspaces) Name of the output the node is located on. 473 | pub output: Option, 474 | } 475 | 476 | #[non_exhaustive] 477 | #[derive(Clone, Debug, Deserialize, Serialize)] 478 | #[serde(rename_all = "lowercase")] 479 | pub struct ColorableBarPart { 480 | /// The color to use for the bar background on unfocused outputs. 481 | pub background: String, 482 | /// The color to use for the status line text on unfocused outputs. 483 | pub statusline: String, 484 | /// The color to use for the separator text on unfocused outputs. 485 | pub separator: String, 486 | /// The color to use for the background of the bar on the focused output. 487 | pub focused_background: String, 488 | /// The color to use for the status line text on the focused output. 489 | pub focused_statusline: String, 490 | /// The color to use for the separator text on the focused output. 491 | pub focused_separator: String, 492 | /// The color to use for the text of the focused workspace button. 493 | pub focused_workspace_text: String, 494 | /// The color to use for the background of the focused workspace button. 495 | pub focused_workspace_bg: String, 496 | /// The color to use for the border of the focused workspace button. 497 | pub focused_workspace_border: String, 498 | /// The color to use for the text of the workspace buttons for the visible 499 | /// workspaces on unfocused outputs. 500 | pub active_workspace_text: String, 501 | /// The color to use for the background of the workspace buttons for the 502 | /// visible workspaces on unfocused outputs. 503 | pub active_workspace_bg: String, 504 | /// The color to use for the border of the workspace buttons for the visible 505 | /// workspaces on unfocused outputs. 506 | pub active_workspace_border: String, 507 | /// The color to use for the text of the workspace buttons for workspaces 508 | /// that are not visible. 509 | pub inactive_workspace_text: String, 510 | /// The color to use for the background of the workspace buttons for 511 | /// workspaces that are not visible. 512 | pub inactive_workspace_bg: String, 513 | /// The color to use for the border of the workspace buttons for workspaces 514 | /// that are not visible. 515 | pub inactive_workspace_border: String, 516 | /// The color to use for the text of the workspace buttons for workspaces 517 | /// that contain an urgent view. 518 | pub urgent_workspace_text: String, 519 | /// The color to use for the background of the workspace buttons for 520 | /// workspaces that contain an urgent view. 521 | pub urgent_workspace_bg: String, 522 | /// The color to use for the border of the workspace buttons for workspaces 523 | /// that contain an urgent view. 524 | pub urgent_workspace_border: String, 525 | /// The color to use for the text of the binding mode indicator. 526 | pub binding_mode_text: String, 527 | /// The color to use for the background of the binding mode indicator. 528 | pub binding_mode_bg: String, 529 | /// The color to use for the border of the binding mode indicator. 530 | pub binding_mode_border: String, 531 | } 532 | 533 | #[non_exhaustive] 534 | #[derive(Clone, Debug, Deserialize, Serialize)] 535 | pub struct BarConfig { 536 | /// The bar ID. 537 | pub id: String, 538 | /// The mode for the bar. It can be dock, hide, or invisible. 539 | pub mode: BarMode, 540 | /// The bar's position. It can currently either be bottom or top. 541 | pub position: Position, 542 | /// The command which should be run to generate the status line. 543 | pub status_command: Option, 544 | /// The font to use for the text on the bar. 545 | pub font: String, 546 | /// Whether to display the workspace buttons on the bar. 547 | pub workspace_buttons: bool, 548 | /// Minimum width in px for the workspace buttons on the bar 549 | #[serde(default)] 550 | pub workspace_min_width: usize, 551 | /// Whether to display the current binding mode on the bar. 552 | pub binding_mode_indicator: bool, 553 | /// For i3 compatibility, this will be the boolean value false. 554 | pub verbose: bool, 555 | /// An object containing the #RRGGBBAA colors to use for the bar. See below 556 | /// for more information. 557 | pub colors: ColorableBarPart, 558 | /// An object representing the gaps for the bar consisting of top, right, 559 | /// bottom, and left. 560 | pub gaps: Gaps, 561 | /// The absolute height to use for the bar or 0 to automatically size based 562 | /// on the font. 563 | pub bar_height: usize, 564 | /// The vertical padding to use for the status line. 565 | pub status_padding: usize, 566 | /// The horizontal padding to use for the status line when at the end of an 567 | /// output. 568 | pub status_edge_padding: usize, 569 | } 570 | 571 | #[non_exhaustive] 572 | #[derive(Clone, Debug, Deserialize, Serialize)] 573 | pub struct BindingState { 574 | /// The currently active binding mode, as a string. 575 | pub name: String, 576 | } 577 | 578 | #[non_exhaustive] 579 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 580 | #[serde(rename_all = "lowercase")] 581 | pub enum BarMode { 582 | Dock, 583 | Hide, 584 | Invisible, 585 | } 586 | 587 | #[non_exhaustive] 588 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 589 | pub struct Gaps { 590 | pub top: usize, 591 | pub bottom: usize, 592 | pub right: usize, 593 | pub left: usize, 594 | } 595 | 596 | #[non_exhaustive] 597 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 598 | #[serde(rename_all = "lowercase")] 599 | pub enum Position { 600 | Bottom, 601 | Top, 602 | } 603 | 604 | #[non_exhaustive] 605 | #[derive(Clone, Debug, Deserialize, Serialize)] 606 | pub struct Version { 607 | /// The major version of the sway process. 608 | pub major: i32, 609 | /// The minor version of the sway process. 610 | pub minor: i32, 611 | /// The patch version of the sway process. 612 | pub patch: i32, 613 | /// A human readable version string that will likely contain more useful 614 | /// information such as the git commit short hash and git branch. 615 | pub human_readable: String, 616 | /// The path to the loaded config file. 617 | pub loaded_config_file_name: String, 618 | } 619 | 620 | #[non_exhaustive] 621 | #[derive(Clone, Debug, Deserialize, Serialize)] 622 | pub struct Config { 623 | /// A single string property containing the contents of the config. 624 | pub config: String, 625 | } 626 | 627 | #[non_exhaustive] 628 | #[derive(Clone, Debug, Deserialize, Serialize)] 629 | pub enum Event { 630 | /// Sent whenever an event involving a workspace occurs such as 631 | /// initialization of a new workspace or a different workspace gains focus. 632 | Workspace(Box), 633 | /// Sent whenever an output is added, removed, or its configuration is changed. 634 | Output(OutputEvent), 635 | /// Sent whenever the binding mode changes. 636 | Mode(ModeEvent), 637 | /// Sent whenever an event involving a view occurs such as being reparented, 638 | /// focused, or closed. 639 | Window(Box), 640 | /// Sent whenever a bar config changes. 641 | BarConfigUpdate(Box), 642 | /// Sent when a configured binding is executed. 643 | Binding(BindingEvent), 644 | /// Sent when the ipc shuts down because sway is exiting. 645 | Shutdown(ShutdownEvent), 646 | /// Sent when an ipc client sends a SEND_TICK message. 647 | Tick(TickEvent), 648 | /// Send when the visibility of a bar should change due to a modifier. 649 | BarStateUpdate(BarStateUpdateEvent), 650 | /// Sent when something related to input devices changes. 651 | Input(Box), 652 | } 653 | 654 | #[non_exhaustive] 655 | #[derive(Clone, Debug, Deserialize, Serialize)] 656 | pub struct InputEvent { 657 | /// What has changed. 658 | pub change: InputChange, 659 | /// An object representing the input that is identical the ones GET_INPUTS 660 | /// gives. 661 | pub input: Input, 662 | } 663 | 664 | #[non_exhaustive] 665 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 666 | #[serde(rename_all = "snake_case")] 667 | pub enum InputChange { 668 | /// The input device became available. 669 | Added, 670 | /// The input device is no longer available. 671 | Removed, 672 | /// (Keyboards only) The keymap for the keyboard has changed. 673 | XkbKeymap, 674 | /// (Keyboards only) The effective layout in the keymap has changed. 675 | XkbLayout, 676 | /// (libinput device only) A libinput config option for the device changed. 677 | LibinputConfig, 678 | } 679 | 680 | #[non_exhaustive] 681 | #[derive(Clone, Debug, Deserialize, Serialize)] 682 | pub struct BarStateUpdateEvent { 683 | /// The bar ID effected. 684 | pub id: String, 685 | /// Whether the bar should be made visible due to a modifier being pressed. 686 | pub visible_by_modifier: bool, 687 | } 688 | 689 | #[non_exhaustive] 690 | #[derive(Clone, Debug, Deserialize, Serialize)] 691 | pub struct TickEvent { 692 | /// Whether this event was triggered by subscribing to the tick events. 693 | pub first: bool, 694 | /// The payload given with a SEND_TICK message, if any. Otherwise, an empty 695 | /// string. 696 | pub payload: String, 697 | } 698 | 699 | #[non_exhaustive] 700 | #[derive(Clone, Debug, Deserialize, Serialize)] 701 | pub struct WorkspaceEvent { 702 | /// The type of change that occurred. 703 | pub change: WorkspaceChange, 704 | /// An object representing the workspace effected or null for reload 705 | /// changes. 706 | pub current: Option, //Only None if WorkspaceChange::Reload 707 | /// For a focus change, this is will be an object representing the workspace 708 | /// being switched from. Otherwise, it is null. 709 | pub old: Option, //Only None if WorkspaceChange::Reload 710 | } 711 | 712 | #[non_exhaustive] 713 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 714 | #[serde(rename_all = "lowercase")] 715 | pub enum OutputChange { 716 | /// We don't know what exactly changed. 717 | Unspecified, 718 | } 719 | 720 | #[non_exhaustive] 721 | #[derive(Clone, Debug, Deserialize, Serialize)] 722 | pub struct OutputEvent { 723 | /// The type of change that occurred. 724 | pub change: OutputChange, 725 | } 726 | 727 | #[non_exhaustive] 728 | #[derive(Clone, Debug, Deserialize, Serialize)] 729 | pub struct ModeEvent { 730 | /// The binding mode that became active. 731 | pub change: String, 732 | /// Whether the mode should be parsed as pango markup. 733 | pub pango_markup: bool, 734 | } 735 | 736 | #[non_exhaustive] 737 | #[derive(Clone, Debug, Deserialize, Serialize)] 738 | pub struct WindowEvent { 739 | /// The type of change that occurred. 740 | pub change: WindowChange, 741 | /// An object representing the view effected. 742 | pub container: Node, 743 | } 744 | 745 | #[non_exhaustive] 746 | #[derive(Clone, Debug, Deserialize, Serialize)] 747 | pub struct BindingEvent { 748 | /// The change that occurred for the binding. Currently this will only be 749 | /// run. 750 | pub change: BindingChange, 751 | /// Details about the binding event. 752 | pub binding: BindingEventOps, 753 | } 754 | 755 | #[non_exhaustive] 756 | #[derive(Clone, Debug, Deserialize, Serialize)] 757 | pub struct BindingEventOps { 758 | /// The command associated with the binding. 759 | pub command: String, 760 | /// An array of strings that correspond to each modifier key for the 761 | /// binding. 762 | #[serde(default)] 763 | pub event_state_mask: Vec, 764 | /// For keyboard bindcodes, this is the key code for the binding. For mouse 765 | /// bindings, this is the X11 button number, if there is an equivalent. In 766 | /// all other cases, this will be 0. 767 | pub input_code: u8, 768 | /// For keyboard bindsyms, this is the bindsym for the binding. Otherwise, 769 | /// this will be null. 770 | pub symbol: Option, 771 | /// The input type that triggered the binding. This is either keyboard or 772 | /// mouse. 773 | pub input_type: InputType, 774 | } 775 | 776 | #[non_exhaustive] 777 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 778 | pub struct ShutdownEvent { 779 | /// The reason for the shutdown. 780 | pub change: ShutdownChange, 781 | } 782 | 783 | #[non_exhaustive] 784 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 785 | #[serde(rename_all = "lowercase")] 786 | pub enum WorkspaceChange { 787 | /// The workspace was created. 788 | Init, 789 | /// The workspace is empty and is being destroyed since it is not visible. 790 | Empty, 791 | /// The workspace was focused. See the old property for the previous focus. 792 | Focus, 793 | /// The workspace was moved to a different output. 794 | Move, 795 | /// The workspace was renamed. 796 | Rename, 797 | /// A view on the workspace has had their urgency hint set or all urgency 798 | /// hints for views on the workspace have been cleared. 799 | Urgent, 800 | /// The configuration file has been reloaded. 801 | Reload, 802 | } 803 | 804 | #[non_exhaustive] 805 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 806 | #[serde(rename_all = "snake_case")] 807 | pub enum WindowChange { 808 | /// The view was created. 809 | New, 810 | /// The view was closed. 811 | Close, 812 | /// The view was focused. 813 | Focus, 814 | /// The view's title has changed. 815 | Title, 816 | /// The view's fullscreen mode has changed. 817 | FullscreenMode, 818 | /// The view has been reparented in the tree. 819 | Move, 820 | /// The view has become floating or is no longer floating. 821 | Floating, 822 | /// The view's urgency hint has changed status. 823 | Urgent, 824 | /// A mark has been added or. 825 | Mark, 826 | } 827 | 828 | #[non_exhaustive] 829 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 830 | #[serde(rename_all = "lowercase")] 831 | pub enum InputType { 832 | Keyboard, 833 | Mouse, 834 | } 835 | 836 | #[non_exhaustive] 837 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 838 | #[serde(rename_all = "lowercase")] 839 | pub enum BindingChange { 840 | Run, 841 | } 842 | 843 | #[non_exhaustive] 844 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 845 | #[serde(rename_all = "lowercase")] 846 | pub enum ShutdownChange { 847 | Exit, 848 | } 849 | 850 | #[non_exhaustive] 851 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 852 | #[serde(rename_all = "snake_case")] 853 | pub enum ShellType { 854 | XdgShell, 855 | Xwayland, 856 | Unknown, 857 | } 858 | --------------------------------------------------------------------------------