diff --git a/Cargo.lock b/Cargo.lock index e4134f02..33d53100 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,6 +347,8 @@ dependencies = [ "komorebi-core", "lazy_static", "nanoid", + "serde", + "serde_json", "strum", "sysinfo", "tracing", diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 0b83b28c..75c206f9 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -51,7 +51,7 @@ pub enum SocketMessage { FloatClass(String), FloatExe(String), FloatTitle(String), - // TODO: Add some state query commands + State, } impl SocketMessage { diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index d7b8c34c..431d71ba 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -1,6 +1,8 @@ +use serde::Serialize; + use bindings::Windows::Win32::Foundation::RECT; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize)] pub struct Rect { pub left: i32, pub top: i32, diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index 8e06bd6a..1e3198bd 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -18,6 +18,8 @@ dirs = "3" eyre = "0.6.5" lazy_static = "1.4.0" nanoid = "0.4.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" strum = { version = "0.21", features = ["derive"] } sysinfo = "0.19" tracing = "0.1.26" diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index 5160f372..0d243672 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -1,12 +1,14 @@ use std::collections::VecDeque; use nanoid::nanoid; +use serde::Serialize; use crate::ring::Ring; use crate::window::Window; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct Container { + #[serde(skip_serializing)] id: String, windows: Ring, } diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index c501f321..5248aafb 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use color_eyre::eyre::ContextCompat; use color_eyre::Result; +use serde::Serialize; use komorebi_core::Rect; @@ -10,12 +11,13 @@ use crate::container::Container; use crate::ring::Ring; use crate::workspace::Workspace; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct Monitor { id: isize, monitor_size: Rect, work_area_size: Rect, workspaces: Ring, + #[serde(skip_serializing)] workspace_names: HashMap, } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 4959e9e4..ddb8a0da 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1,7 +1,9 @@ use std::io::BufRead; use std::io::BufReader; +use std::io::Write; use std::str::FromStr; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use std::sync::Mutex; use std::thread; use color_eyre::eyre::ContextCompat; @@ -41,6 +43,7 @@ pub fn listen_for_commands(wm: Arc>) { } impl WindowManager { + #[allow(clippy::too_many_lines)] pub fn process_command(&mut self, stream: UnixStream) -> Result<()> { let stream = BufReader::new(stream); for line in stream.lines() { @@ -142,6 +145,15 @@ impl WindowManager { SocketMessage::WorkspaceName(monitor_idx, workspace_idx, name) => { self.set_workspace_name(monitor_idx, workspace_idx, name)?; } + SocketMessage::State => { + let state = serde_json::to_string_pretty(self)?; + let mut socket = dirs::home_dir().context("there is no home directory")?; + socket.push("komorebic.sock"); + let socket = socket.as_path(); + + let mut stream = UnixStream::connect(&socket)?; + stream.write_all(state.as_bytes())?; + } } } diff --git a/komorebi/src/ring.rs b/komorebi/src/ring.rs index a75ad8d9..2e2536e8 100644 --- a/komorebi/src/ring.rs +++ b/komorebi/src/ring.rs @@ -1,6 +1,8 @@ use std::collections::VecDeque; -#[derive(Debug, Clone)] +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] pub struct Ring { elements: VecDeque, focused: usize, diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 141c69ba..abb61689 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -4,6 +4,9 @@ use std::fmt::Formatter; use color_eyre::eyre::ContextCompat; use color_eyre::Result; +use serde::ser::SerializeStruct; +use serde::Serialize; +use serde::Serializer; use bindings::Windows::Win32::Foundation::HWND; use komorebi_core::Rect; @@ -45,6 +48,24 @@ impl Display for Window { } } +impl Serialize for Window { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("Window", 5)?; + state.serialize_field("hwnd", &self.hwnd)?; + state.serialize_field("title", &self.title().expect("could not get window title"))?; + state.serialize_field("exe", &self.exe().expect("could not get window exe"))?; + state.serialize_field("class", &self.class().expect("could not get window class"))?; + state.serialize_field( + "rect", + &WindowsApi::window_rect(self.hwnd()).expect("could not get window rect"), + )?; + state.end() + } +} + impl Window { pub const fn hwnd(&self) -> HWND { HWND(self.hwnd) diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 573f54c4..ecbecd1e 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -6,6 +6,7 @@ use std::sync::Mutex; use color_eyre::eyre::ContextCompat; use color_eyre::Result; use crossbeam_channel::Receiver; +use serde::Serialize; use uds_windows::UnixListener; use komorebi_core::CycleDirection; @@ -23,10 +24,12 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::workspace::Workspace; -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct WindowManager { pub monitors: Ring, + #[serde(skip_serializing)] pub incoming_events: Arc>>, + #[serde(skip_serializing)] pub command_listener: UnixListener, pub is_paused: bool, } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index af6fcb8b..062f4c78 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use color_eyre::eyre::ContextCompat; use color_eyre::Result; +use serde::Serialize; use komorebi_core::Layout; use komorebi_core::LayoutFlip; @@ -13,17 +14,19 @@ use crate::ring::Ring; use crate::window::Window; use crate::windows_api::WindowsApi; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct Workspace { name: Option, containers: Ring, monocle_container: Option, + #[serde(skip_serializing)] monocle_restore_idx: Option, floating_windows: Vec, layout: Layout, layout_flip: Option, workspace_padding: Option, container_padding: Option, + #[serde(skip_serializing)] latest_layout: Vec, } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 07f4c9c2..7633ca1c 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -1,7 +1,12 @@ +use std::io::BufRead; +use std::io::BufReader; +use std::io::ErrorKind; use std::io::Write; use clap::Clap; +use color_eyre::eyre::ContextCompat; use color_eyre::Result; +use uds_windows::UnixListener; use uds_windows::UnixStream; use komorebi_core::CycleDirection; @@ -39,6 +44,7 @@ enum SubCommand { ToggleFloat, TogglePause, ToggleMonocle, + State, Start, Stop, FloatClass(FloatTarget), @@ -92,19 +98,13 @@ struct FloatTarget { id: String, } -pub fn send_message(bytes: &[u8]) { - let mut socket = dirs::home_dir().unwrap(); +pub fn send_message(bytes: &[u8]) -> Result<()> { + let mut socket = dirs::home_dir().context("there is no home directory")?; socket.push("komorebi.sock"); let socket = socket.as_path(); - let mut stream = match UnixStream::connect(&socket) { - Err(_) => panic!("server is not running"), - Ok(stream) => stream, - }; - - if stream.write_all(&*bytes).is_err() { - panic!("couldn't send message") - } + let mut stream = UnixStream::connect(&socket)?; + Ok(stream.write_all(&*bytes)?) } fn main() -> Result<()> { @@ -113,47 +113,47 @@ fn main() -> Result<()> { match opts.subcmd { SubCommand::Focus(direction) => { let bytes = SocketMessage::FocusWindow(direction).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Promote => { let bytes = SocketMessage::Promote.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::TogglePause => { let bytes = SocketMessage::TogglePause.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Retile => { let bytes = SocketMessage::Retile.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Move(direction) => { let bytes = SocketMessage::MoveWindow(direction).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::MoveToMonitor(display) => { let bytes = SocketMessage::MoveContainerToMonitorNumber(display.number) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::MoveToWorkspace(workspace) => { let bytes = SocketMessage::MoveContainerToWorkspaceNumber(workspace.number) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::ContainerPadding(gap) => { let bytes = SocketMessage::ContainerPadding(gap.monitor, gap.workspace, gap.size) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::WorkspacePadding(gap) => { let bytes = SocketMessage::WorkspacePadding(gap.monitor, gap.workspace, gap.size) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::AdjustWorkspacePadding(sizing_adjustment) => { let bytes = SocketMessage::AdjustWorkspacePadding( @@ -162,7 +162,7 @@ fn main() -> Result<()> { ) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::AdjustContainerPadding(sizing_adjustment) => { let bytes = SocketMessage::AdjustContainerPadding( @@ -171,22 +171,22 @@ fn main() -> Result<()> { ) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::ToggleFloat => { let bytes = SocketMessage::ToggleFloat.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::ToggleMonocle => { let bytes = SocketMessage::ToggleMonocle.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::WorkspaceLayout(layout) => { let bytes = SocketMessage::WorkspaceLayout(layout.monitor, layout.workspace, layout.layout) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Start => { let script = r#"Start-Process komorebi -WindowStyle hidden"#; @@ -201,60 +201,95 @@ fn main() -> Result<()> { } SubCommand::Stop => { let bytes = SocketMessage::Stop.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FloatClass(target) => { let bytes = SocketMessage::FloatClass(target.id).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FloatExe(target) => { let bytes = SocketMessage::FloatExe(target.id).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FloatTitle(target) => { let bytes = SocketMessage::FloatTitle(target.id).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Stack(direction) => { let bytes = SocketMessage::StackWindow(direction).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::Unstack => { let bytes = SocketMessage::UnstackWindow.as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::CycleStack(direction) => { let bytes = SocketMessage::CycleStack(direction).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FlipLayout(flip) => { let bytes = SocketMessage::FlipLayout(flip).as_bytes()?; - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FocusMonitor(target) => { let bytes = SocketMessage::FocusMonitorNumber(target.number) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::FocusWorkspace(target) => { let bytes = SocketMessage::FocusWorkspaceNumber(target.number) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::WorkspaceName(name) => { let bytes = SocketMessage::WorkspaceName(name.monitor, name.workspace, name.value) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; } SubCommand::EnsureWorkspaces(workspaces) => { let bytes = SocketMessage::EnsureWorkspaces(workspaces.monitor, workspaces.workspace_count) .as_bytes() .unwrap(); - send_message(&*bytes); + send_message(&*bytes)?; + } + SubCommand::State => { + let home = dirs::home_dir().context("there is no home directory")?; + let mut socket = home; + socket.push("komorebic.sock"); + let socket = socket.as_path(); + + match std::fs::remove_file(&socket) { + Ok(_) => {} + Err(error) => match error.kind() { + // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ + ErrorKind::NotFound => {} + _ => { + return Err(error.into()); + } + }, + }; + + let bytes = SocketMessage::State.as_bytes().unwrap(); + send_message(&*bytes)?; + + let listener = UnixListener::bind(&socket)?; + match listener.accept() { + Ok(incoming) => { + let stream = BufReader::new(incoming.0); + for line in stream.lines() { + println!("{}", line?) + } + + return Ok(()); + } + Err(error) => { + panic!("{}", error) + } + } } }