mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-05-08 20:03:37 +02:00
feat(wm): add query command to cli
Added a query command to komorebic to return the WindowManager struct serialized to JSON to help with debugging and maybe help others to build tools like stackline for yabai in the future.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -347,6 +347,8 @@ dependencies = [
|
||||
"komorebi-core",
|
||||
"lazy_static",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"sysinfo",
|
||||
"tracing",
|
||||
|
||||
@@ -51,7 +51,7 @@ pub enum SocketMessage {
|
||||
FloatClass(String),
|
||||
FloatExe(String),
|
||||
FloatTitle(String),
|
||||
// TODO: Add some state query commands
|
||||
State,
|
||||
}
|
||||
|
||||
impl SocketMessage {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<Window>,
|
||||
}
|
||||
|
||||
@@ -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<Workspace>,
|
||||
#[serde(skip_serializing)]
|
||||
workspace_names: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Mutex<WindowManager>>) {
|
||||
}
|
||||
|
||||
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())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Ring<T> {
|
||||
elements: VecDeque<T>,
|
||||
focused: usize,
|
||||
|
||||
@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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)
|
||||
|
||||
@@ -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<Monitor>,
|
||||
#[serde(skip_serializing)]
|
||||
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
|
||||
#[serde(skip_serializing)]
|
||||
pub command_listener: UnixListener,
|
||||
pub is_paused: bool,
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
containers: Ring<Container>,
|
||||
monocle_container: Option<Container>,
|
||||
#[serde(skip_serializing)]
|
||||
monocle_restore_idx: Option<usize>,
|
||||
floating_windows: Vec<Window>,
|
||||
layout: Layout,
|
||||
layout_flip: Option<LayoutFlip>,
|
||||
workspace_padding: Option<i32>,
|
||||
container_padding: Option<i32>,
|
||||
#[serde(skip_serializing)]
|
||||
latest_layout: Vec<Rect>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user