feat(cli): add visible-windows cmd

This commit adds a new komorebic command, "visible-windows", to make
tracking down ghost windows easier. The returned JSON structure will try
to use the device id to identify a monitor if it is available, or fall
back to the monitor index. Thanks to raggi on Discord for suggesting
this command!
This commit is contained in:
LGUG2Z
2024-01-02 10:37:21 -08:00
parent d2a06a11ac
commit b4ae043b9c
5 changed files with 69 additions and 0 deletions

View File

@@ -147,6 +147,7 @@ pub enum SocketMessage {
IdentifyLayeredApplication(ApplicationIdentifier, String),
IdentifyBorderOverflowApplication(ApplicationIdentifier, String),
State,
VisibleWindows,
Query(StateQuery),
FocusFollowsMouse(FocusFollowsMouseImplementation, bool),
ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),

View File

@@ -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(),

View File

@@ -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<Window> for WindowDetails {
type Error = eyre::ErrReport;
fn try_from(value: Window) -> std::result::Result<Self, Self::Error> {
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);

View File

@@ -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<WindowDetails> {
let mut vec: Vec<WindowDetails> = 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<Option<&mut Window>> {
let mut vec = vec![];
for container in self.containers_mut() {

View File

@@ -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()?)