diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 56c2d517..7905bb59 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -147,6 +147,7 @@ pub enum SocketMessage { IdentifyLayeredApplication(ApplicationIdentifier, String), IdentifyBorderOverflowApplication(ApplicationIdentifier, String), State, + VisibleWindows, Query(StateQuery), FocusFollowsMouse(FocusFollowsMouseImplementation, bool), ToggleFocusFollowsMouse(FocusFollowsMouseImplementation), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index de3cf8bf..e529e5df 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::File; use std::fs::OpenOptions; use std::io::BufRead; @@ -701,6 +702,32 @@ impl WindowManager { let mut stream = UnixStream::connect(socket)?; stream.write_all(state.as_bytes())?; } + SocketMessage::VisibleWindows => { + let mut monitor_visible_windows = HashMap::new(); + + for (index, monitor) in self.monitors().iter().enumerate() { + if let Some(ws) = monitor.focused_workspace() { + monitor_visible_windows.insert( + monitor + .device_id() + .clone() + .unwrap_or_else(|| format!("{index}")), + ws.visible_window_details().clone(), + ); + } + } + + let visible_windows_state = + match serde_json::to_string_pretty(&monitor_visible_windows) { + Ok(state) => state, + Err(error) => error.to_string(), + }; + + let socket = DATA_DIR.join("komorebic.sock"); + let mut stream = UnixStream::connect(socket)?; + stream.write_all(visible_windows_state.as_bytes())?; + } + SocketMessage::Query(query) => { let response = match query { StateQuery::FocusedMonitorIndex => self.focused_monitor_idx(), diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index b991baac..47574a43 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -6,6 +6,7 @@ use std::fmt::Formatter; use std::fmt::Write as _; use std::sync::atomic::Ordering; +use color_eyre::eyre; use color_eyre::eyre::anyhow; use color_eyre::Result; use komorebi_core::config_generation::IdWithIdentifier; @@ -46,6 +47,26 @@ pub struct Window { pub(crate) hwnd: isize, } +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Serialize, JsonSchema)] +pub struct WindowDetails { + pub title: String, + pub exe: String, + pub class: String, +} + +impl TryFrom for WindowDetails { + type Error = eyre::ErrReport; + + fn try_from(value: Window) -> std::result::Result { + Ok(Self { + title: value.title()?, + exe: value.exe()?, + class: value.class()?, + }) + } +} + impl Display for Window { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut display = format!("(hwnd: {}", self.hwnd); diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index eddfd5bc..be5641ea 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -23,6 +23,7 @@ use crate::container::Container; use crate::ring::Ring; use crate::static_config::WorkspaceConfig; use crate::window::Window; +use crate::window::WindowDetails; use crate::windows_api::WindowsApi; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; @@ -1087,6 +1088,20 @@ impl Workspace { vec } + pub fn visible_window_details(&self) -> Vec { + let mut vec: Vec = vec![]; + + for container in self.containers() { + if let Some(focused) = container.focused_window() { + if let Ok(details) = (*focused).try_into() { + vec.push(details); + } + } + } + + vec + } + pub fn visible_windows_mut(&mut self) -> Vec> { let mut vec = vec![]; for container in self.containers_mut() { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 56123d60..877a17a1 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -773,6 +773,8 @@ enum SubCommand { Check, /// Show a JSON representation of the current window manager state State, + /// Show a JSON representation of visible windows + VisibleWindows, /// Query the current window manager state #[clap(arg_required_else_help = true)] Query(Query), @@ -1921,6 +1923,9 @@ Stop-Process -Name:whkd -ErrorAction SilentlyContinue SubCommand::State => { with_komorebic_socket(|| send_message(&SocketMessage::State.as_bytes()?))?; } + SubCommand::VisibleWindows => { + with_komorebic_socket(|| send_message(&SocketMessage::VisibleWindows.as_bytes()?))?; + } SubCommand::Query(arg) => { with_komorebic_socket(|| { send_message(&SocketMessage::Query(arg.state_query).as_bytes()?)