From 1671f31e3ea5aaafabc4955584a29f4550b1290b Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Mon, 15 Apr 2024 15:05:18 -0700 Subject: [PATCH] feat(wm): add window debugging This commit adds support for debugging windows and emitting information about how they go through komorebi's decision making pipeline and rules engines which ultimately decide how they are or aren't managed. --- Cargo.lock | 39 +++++----- komorebi-client/src/lib.rs | 1 + komorebi-core/src/lib.rs | 1 + komorebi/Cargo.toml | 2 +- komorebi/src/lib.rs | 1 + komorebi/src/process_command.rs | 9 +++ komorebi/src/process_event.rs | 8 ++- komorebi/src/styles.rs | 6 +- komorebi/src/window.rs | 104 ++++++++++++++++++++++----- komorebi/src/window_manager_event.rs | 3 +- komorebi/src/windows_callbacks.rs | 3 +- 11 files changed, 134 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 905985bf..4783055f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bumpalo" @@ -196,7 +199,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -666,9 +669,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "9f24ce812868d86d19daa79bf3bf9175bc44ea323391147a5e3abde2a283871b" dependencies = [ "bytes", "futures-channel", @@ -1007,7 +1010,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -1201,7 +1204,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -1322,7 +1325,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -1387,9 +1390,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" dependencies = [ "unicode-ident", ] @@ -1695,7 +1698,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -1821,7 +1824,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -1858,9 +1861,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" dependencies = [ "proc-macro2", "quote", @@ -1959,7 +1962,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -2128,7 +2131,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -2317,7 +2320,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", "wasm-bindgen-shared", ] @@ -2351,7 +2354,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2470,7 +2473,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] @@ -2481,7 +2484,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.59", ] [[package]] diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index 969fd340..621753db 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -13,6 +13,7 @@ pub use komorebi::ActiveWindowBorderColours; pub use komorebi::GlobalState; pub use komorebi::Notification; pub use komorebi::NotificationEvent; +pub use komorebi::RuleDebug; pub use komorebi::StackbarConfig; pub use komorebi::State; pub use komorebi::TabsConfig; diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 8891946a..5081f732 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -173,6 +173,7 @@ pub enum SocketMessage { SocketSchema, StaticConfigSchema, GenerateStaticConfig, + DebugWindow(isize), } impl SocketMessage { diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index b8922a96..45f87b0a 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] komorebi-core = { path = "../komorebi-core" } -bitflags = "2" +bitflags = { version = "2", features = ["serde"] } clap = { version = "4", features = ["derive"] } color-eyre = { workspace = true } crossbeam-channel = "0.5" diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 016162ae..ea36ca94 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -43,6 +43,7 @@ pub use process_command::*; pub use process_event::*; pub use stackbar::*; pub use static_config::*; +pub use window::*; pub use window_manager::*; pub use window_manager_event::*; pub use windows_api::WindowsApi; diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 60a012eb..146a8c06 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -44,6 +44,7 @@ use crate::colour::Rgb; use crate::current_virtual_desktop; use crate::notify_subscribers; use crate::static_config::StaticConfig; +use crate::window::RuleDebug; use crate::window::Window; use crate::window_manager; use crate::window_manager::WindowManager; @@ -1345,6 +1346,14 @@ impl WindowManager { REMOVE_TITLEBARS.store(!current, Ordering::SeqCst); self.update_focused_workspace(false, false)?; } + SocketMessage::DebugWindow(hwnd) => { + let window = Window { hwnd }; + let mut rule_debug = RuleDebug::default(); + let _ = window.should_manage(None, &mut rule_debug); + let schema = serde_json::to_string_pretty(&rule_debug)?; + + reply.write_all(schema.as_bytes())?; + } // Deprecated commands SocketMessage::AltFocusHack(_) | SocketMessage::IdentifyBorderOverflowApplication(_, _) => {} diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index dddddd9f..d16422ea 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -15,6 +15,7 @@ use crate::border::Border; use crate::current_virtual_desktop; use crate::notify_subscribers; use crate::window::should_act; +use crate::window::RuleDebug; use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; @@ -66,7 +67,9 @@ impl WindowManager { return Ok(()); } - let should_manage = event.window().should_manage(Some(event))?; + let mut rule_debug = RuleDebug::default(); + + let should_manage = event.window().should_manage(Some(event), &mut rule_debug)?; // Hide or reposition the window based on whether the target is managed. if BORDER_ENABLED.load(Ordering::SeqCst) { @@ -233,7 +236,8 @@ impl WindowManager { path, &tray_and_multi_window_identifiers, ®ex_identifiers, - ); + ) + .is_some(); if !window.is_window() || should_act diff --git a/komorebi/src/styles.rs b/komorebi/src/styles.rs index 54f35f83..6ecf17c5 100644 --- a/komorebi/src/styles.rs +++ b/komorebi/src/styles.rs @@ -1,4 +1,6 @@ use bitflags::bitflags; +use serde::Deserialize; +use serde::Serialize; use windows::Win32::UI::WindowsAndMessaging::WS_BORDER; use windows::Win32::UI::WindowsAndMessaging::WS_CAPTION; use windows::Win32::UI::WindowsAndMessaging::WS_CHILD; @@ -56,7 +58,7 @@ use windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL; // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles bitflags! { - #[derive(Default)] + #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] pub struct WindowStyle: u32 { const BORDER = WS_BORDER.0; const CAPTION = WS_CAPTION.0; @@ -90,7 +92,7 @@ bitflags! { // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles bitflags! { - #[derive(Default)] + #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)] pub struct ExtendedWindowStyle: u32 { const ACCEPTFILES = WS_EX_ACCEPTFILES.0; const APPWINDOW = WS_EX_APPWINDOW.0; diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 11539b0e..23770735 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -41,7 +41,7 @@ use crate::WSL2_UI_PROCESSES; #[derive(Debug, Clone, Copy, Deserialize, JsonSchema)] pub struct Window { - pub(crate) hwnd: isize, + pub hwnd: isize, } #[allow(clippy::module_name_repetitions)] @@ -153,6 +153,10 @@ impl Window { WindowsApi::is_iconic(self.hwnd()) } + pub fn is_visible(self) -> bool { + WindowsApi::is_window_visible(self.hwnd()) + } + pub fn hide(self) { let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock(); if !programmatically_hidden_hwnds.contains(&self.hwnd) { @@ -313,18 +317,28 @@ impl Window { self.update_style(&style) } - #[tracing::instrument(fields(exe, title))] - pub fn should_manage(self, event: Option) -> Result { + #[tracing::instrument(fields(exe, title), skip(debug))] + pub fn should_manage( + self, + event: Option, + debug: &mut RuleDebug, + ) -> Result { if !self.is_window() { return Ok(false); } + debug.is_window = true; + if self.title().is_err() { return Ok(false); } + debug.has_title = true; + let is_cloaked = self.is_cloaked().unwrap_or_default(); + debug.is_cloaked = is_cloaked; + let mut allow_cloaked = false; if let Some(event) = event { @@ -336,17 +350,27 @@ impl Window { } } + debug.allow_cloaked = allow_cloaked; + match (allow_cloaked, is_cloaked) { // If allowing cloaked windows, we don't need to check the cloaked status (true, _) | // If not allowing cloaked windows, we need to ensure the window is not cloaked (false, false) => { if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (self.title(), self.exe(), self.class(), self.path()) { + debug.title = Some(title.clone()); + debug.exe_name = Some(exe_name.clone()); + debug.class = Some(class.clone()); + debug.path = Some(path.clone()); // calls for styles can fail quite often for events with windows that aren't really "windows" // since we have moved up calls of should_manage to the beginning of the process_event handler, // we should handle failures here gracefully to be able to continue the execution of process_event if let (Ok(style), Ok(ex_style)) = (&self.style(), &self.ex_style()) { - return Ok(window_is_eligible(&title, &exe_name, &class, &path, style, ex_style, event)); + debug.window_style = Some(*style); + debug.extended_window_style = Some(*ex_style); + let eligible = window_is_eligible(&title, &exe_name, &class, &path, style, ex_style, event, debug); + debug.should_manage = eligible; + return Ok(eligible); } } } @@ -357,6 +381,28 @@ impl Window { } } +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct RuleDebug { + pub should_manage: bool, + pub is_window: bool, + pub has_title: bool, + pub is_cloaked: bool, + pub allow_cloaked: bool, + pub window_style: Option, + pub extended_window_style: Option, + pub title: Option, + pub exe_name: Option, + pub class: Option, + pub path: Option, + pub matches_permaignore_class: Option, + pub matches_float_identifier: Option, + pub matches_managed_override: Option, + pub matches_layered_whitelist: Option, + pub matches_wsl2_gui: Option, + pub matches_no_titlebar: Option, +} + +#[allow(clippy::too_many_arguments)] fn window_is_eligible( title: &String, exe_name: &String, @@ -365,10 +411,12 @@ fn window_is_eligible( style: &WindowStyle, ex_style: &ExtendedWindowStyle, event: Option, + debug: &mut RuleDebug, ) -> bool { { let permaignore_classes = PERMAIGNORE_CLASSES.lock(); if permaignore_classes.contains(class) { + debug.matches_permaignore_class = Some(class.clone()); return false; } } @@ -376,45 +424,65 @@ fn window_is_eligible( let regex_identifiers = REGEX_IDENTIFIERS.lock(); let float_identifiers = FLOAT_IDENTIFIERS.lock(); - let should_float = should_act( + let should_float = if let Some(rule) = should_act( title, exe_name, class, path, &float_identifiers, ®ex_identifiers, - ); + ) { + debug.matches_float_identifier = Some(rule); + true + } else { + false + }; let manage_identifiers = MANAGE_IDENTIFIERS.lock(); - let managed_override = should_act( + let managed_override = if let Some(rule) = should_act( title, exe_name, class, path, &manage_identifiers, ®ex_identifiers, - ); + ) { + debug.matches_managed_override = Some(rule); + true + } else { + false + }; if should_float && !managed_override { return false; } let layered_whitelist = LAYERED_WHITELIST.lock(); - let allow_layered = should_act( + let allow_layered = if let Some(rule) = should_act( title, exe_name, class, path, &layered_whitelist, ®ex_identifiers, - ); + ) { + debug.matches_layered_whitelist = Some(rule); + true + } else { + false + }; // TODO: might need this for transparency // let allow_layered = true; let allow_wsl2_gui = { let wsl2_ui_processes = WSL2_UI_PROCESSES.lock(); - wsl2_ui_processes.contains(exe_name) + let allow = wsl2_ui_processes.contains(exe_name); + if allow { + debug.matches_wsl2_gui = Some(exe_name.clone()) + } + + allow }; let allow_titlebar_removed = { @@ -456,10 +524,10 @@ pub fn should_act( path: &str, identifiers: &[MatchingRule], regex_identifiers: &HashMap, -) -> bool { - let mut should_act = false; - for identifier in identifiers { - match identifier { +) -> Option { + let mut matching_rule = None; + for rule in identifiers { + match rule { MatchingRule::Simple(identifier) => { if should_act_individual( title, @@ -469,7 +537,7 @@ pub fn should_act( identifier, regex_identifiers, ) { - should_act = true + matching_rule = Some(rule.clone()); }; } MatchingRule::Composite(identifiers) => { @@ -486,13 +554,13 @@ pub fn should_act( } if composite_results.iter().all(|&x| x) { - should_act = true; + matching_rule = Some(rule.clone()); } } } } - should_act + matching_rule } pub fn should_act_individual( diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index afa30873..2726bc3d 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -153,7 +153,8 @@ impl WindowManagerEvent { path, &object_name_change_on_launch, ®ex_identifiers, - ); + ) + .is_some(); if should_trigger { Option::from(Self::Show(winevent, window)) diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 94055081..f1bac55a 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -36,6 +36,7 @@ use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE; use crate::container::Container; use crate::monitor::Monitor; use crate::ring::Ring; +use crate::window::RuleDebug; use crate::window::Window; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; @@ -157,7 +158,7 @@ pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL { if is_visible && is_window && !is_minimized { let window = Window { hwnd: hwnd.0 }; - if let Ok(should_manage) = window.should_manage(None) { + if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default()) { if should_manage { if is_maximized { WindowsApi::restore_window(hwnd);