From cf7108de944b3ca24e68ee9ee6243411675630a4 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Wed, 19 Jun 2024 11:28:16 -0700 Subject: [PATCH] think this still leaves us with inconsistent state --- Cargo.lock | 3 + Cargo.toml | 4 +- komorebi/Cargo.toml | 3 +- komorebi/src/process_event.rs | 41 +++++--- komorebi/src/window.rs | 2 +- komorebi/src/window_manager.rs | 7 +- komorebi/src/window_manager_event.rs | 140 ++++++++++++++++----------- komorebi/src/windows_api.rs | 4 + komorebi/src/windows_callbacks.rs | 21 +++- 9 files changed, 146 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eee33c62..24e22085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -794,6 +794,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.5", ] @@ -2304,6 +2305,7 @@ name = "komorebi" version = "0.1.27-dev.0" dependencies = [ "bitflags 2.5.0", + "chrono", "clap", "color-eyre", "crossbeam-channel", @@ -3734,6 +3736,7 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ + "chrono", "dyn-clone", "schemars_derive", "serde", diff --git a/Cargo.toml b/Cargo.toml index 805e7486..9bca8ed0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,5 +40,7 @@ features = [ "Win32_UI_Shell", "Win32_UI_Shell_Common", "Win32_UI_WindowsAndMessaging", - "Win32_System_SystemServices" + "Win32_System", + "Win32_System_SystemServices", + "Win32_System_SystemInformation" ] diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index 618643fc..140da1df 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -19,6 +19,7 @@ color-eyre = { workspace = true } crossbeam-channel = "0.5" crossbeam-utils = "0.8" ctrlc = { version = "3", features = ["termination"] } +chrono = { version = "0.4", features = ["serde"] } dirs = { workspace = true } getset = "0.1" hex_color = { version = "3", features = ["serde"] } @@ -31,7 +32,7 @@ os_info = "3.8" parking_lot = "0.12" paste = "1" regex = "1" -schemars = "0.8" +schemars = { version = "0.8", features = ["chrono"] } serde = { version = "1", features = ["derive"] } serde_json = { workspace = true } strum = { version = "0.26", features = ["derive"] } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 44db8376..bbf229a7 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -1,3 +1,5 @@ +use chrono::DateTime; +use chrono::Utc; use std::fs::OpenOptions; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -70,6 +72,13 @@ impl WindowManager { return Ok(()); } + let now: DateTime = Utc::now(); + let difference = now - event.timestamp(); + if difference > chrono::Duration::seconds(2) { + tracing::warn!("ignoring event more than two seconds old: {}", event); + return Ok(()); + } + let mut rule_debug = RuleDebug::default(); let should_manage = event.window().should_manage(Some(event), &mut rule_debug)?; @@ -119,9 +128,9 @@ impl WindowManager { // Make sure we have the most recently focused monitor from any event match event { - WindowManagerEvent::FocusChange(_, window) - | WindowManagerEvent::Show(_, window) - | WindowManagerEvent::MoveResizeEnd(_, window) => { + WindowManagerEvent::FocusChange(_, window, _) + | WindowManagerEvent::Show(_, window, _) + | WindowManagerEvent::MoveResizeEnd(_, window, _) => { if let Some(monitor_idx) = self.monitor_idx_from_window(window) { // This is a hidden window apparently associated with COM support mechanisms (based // on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm) @@ -151,7 +160,7 @@ impl WindowManager { for monitor in self.monitors_mut() { for workspace in monitor.workspaces_mut() { - if let WindowManagerEvent::FocusChange(_, window) = event { + if let WindowManagerEvent::FocusChange(_, window, _) = event { let _ = workspace.focus_changed(window.hwnd); } } @@ -167,11 +176,11 @@ impl WindowManager { } match event { - WindowManagerEvent::Raise(window) => { + WindowManagerEvent::Raise(window, _) => { window.focus(false)?; self.has_pending_raise_op = false; } - WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => { + WindowManagerEvent::Destroy(_, window, _) | WindowManagerEvent::Unmanage(window, _) => { if self.focused_workspace()?.contains_window(window.hwnd) { self.focused_workspace_mut()?.remove_window(window.hwnd)?; self.update_focused_workspace(false, false)?; @@ -181,7 +190,7 @@ impl WindowManager { already_moved_window_handles.remove(&window.hwnd); } } - WindowManagerEvent::Minimize(_, window) => { + WindowManagerEvent::Minimize(_, window, _) => { let mut hide = false; { @@ -196,7 +205,7 @@ impl WindowManager { self.update_focused_workspace(false, false)?; } } - WindowManagerEvent::Hide(_, window) => { + WindowManagerEvent::Hide(_, window, _) => { let mut hide = false; // Some major applications unfortunately send the HIDE signal when they are being // minimized or destroyed. Applications that close to the tray also do the same, @@ -242,7 +251,7 @@ impl WindowManager { already_moved_window_handles.remove(&window.hwnd); } - WindowManagerEvent::FocusChange(_, window) => { + WindowManagerEvent::FocusChange(_, window, _) => { self.update_focused_workspace(self.mouse_follows_focus, false)?; let workspace = self.focused_workspace_mut()?; @@ -267,9 +276,9 @@ impl WindowManager { } } } - WindowManagerEvent::Show(_, window) - | WindowManagerEvent::Manage(window) - | WindowManagerEvent::Uncloak(_, window) => { + WindowManagerEvent::Show(_, window, _) + | WindowManagerEvent::Manage(window, _) + | WindowManagerEvent::Uncloak(_, window, _) => { let focused_monitor_idx = self.focused_monitor_idx(); let focused_workspace_idx = self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?; @@ -366,7 +375,7 @@ impl WindowManager { } } } - WindowManagerEvent::MoveResizeStart(_, window) => { + WindowManagerEvent::MoveResizeStart(_, window, _) => { if *self.focused_workspace()?.tile() { let monitor_idx = self.focused_monitor_idx(); let workspace_idx = self @@ -386,7 +395,7 @@ impl WindowManager { Option::from((monitor_idx, workspace_idx, container_idx)); } } - WindowManagerEvent::MoveResizeEnd(_, window) => { + WindowManagerEvent::MoveResizeEnd(_, window, _) => { // We need this because if the event ends on a different monitor, // that monitor will already have been focused and updated in the state let pending = self.pending_move_op; @@ -599,7 +608,7 @@ impl WindowManager { }; // If we unmanaged a window, it shouldn't be immediately hidden behind managed windows - if let WindowManagerEvent::Unmanage(window) = event { + if let WindowManagerEvent::Unmanage(window, _) = event { window.center(&self.focused_monitor_work_area()?)?; } @@ -637,7 +646,7 @@ impl WindowManager { // Too many spammy OBJECT_NAMECHANGE events from JetBrains IDEs if !matches!( event, - WindowManagerEvent::Show(WinEvent::ObjectNameChange, _) + WindowManagerEvent::Show(WinEvent::ObjectNameChange, _, _) ) { tracing::info!("processed: {}", event.window().to_string()); } else { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 9e513ca4..27a7b608 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -367,7 +367,7 @@ impl Window { if let Some(event) = event { if matches!( event, - WindowManagerEvent::Hide(_, _) | WindowManagerEvent::Cloak(_, _) + WindowManagerEvent::Hide(_, _, _) | WindowManagerEvent::Cloak(_, _, _) ) { allow_cloaked = true; } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index cf1d2cfe..49d073d2 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -1,3 +1,4 @@ +use chrono::Utc; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; @@ -598,14 +599,14 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn manage_focused_window(&mut self) -> Result<()> { let hwnd = WindowsApi::foreground_window()?; - let event = WindowManagerEvent::Manage(Window::from(hwnd)); + let event = WindowManagerEvent::Manage(Window::from(hwnd), Utc::now()); Ok(winevent_listener::event_tx().send(event)?) } #[tracing::instrument(skip(self))] pub fn unmanage_focused_window(&mut self) -> Result<()> { let hwnd = WindowsApi::foreground_window()?; - let event = WindowManagerEvent::Unmanage(Window::from(hwnd)); + let event = WindowManagerEvent::Unmanage(Window::from(hwnd), Utc::now()); Ok(winevent_listener::event_tx().send(event)?) } @@ -662,7 +663,7 @@ impl WindowManager { return Ok(()); } - let event = WindowManagerEvent::Raise(Window::from(hwnd)); + let event = WindowManagerEvent::Raise(Window::from(hwnd), Utc::now()); self.has_pending_raise_op = true; winevent_listener::event_tx().send(event)?; } else { diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index 01d0e239..949767ee 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -1,3 +1,5 @@ +use chrono::DateTime; +use chrono::Utc; use std::fmt::Display; use std::fmt::Formatter; @@ -14,68 +16,68 @@ use crate::REGEX_IDENTIFIERS; #[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "type", content = "content")] pub enum WindowManagerEvent { - Destroy(WinEvent, Window), - FocusChange(WinEvent, Window), - Hide(WinEvent, Window), - Cloak(WinEvent, Window), - Minimize(WinEvent, Window), - Show(WinEvent, Window), - Uncloak(WinEvent, Window), - MoveResizeStart(WinEvent, Window), - MoveResizeEnd(WinEvent, Window), - MouseCapture(WinEvent, Window), - Manage(Window), - Unmanage(Window), - Raise(Window), - TitleUpdate(WinEvent, Window), + Destroy(WinEvent, Window, DateTime), + FocusChange(WinEvent, Window, DateTime), + Hide(WinEvent, Window, DateTime), + Cloak(WinEvent, Window, DateTime), + Minimize(WinEvent, Window, DateTime), + Show(WinEvent, Window, DateTime), + Uncloak(WinEvent, Window, DateTime), + MoveResizeStart(WinEvent, Window, DateTime), + MoveResizeEnd(WinEvent, Window, DateTime), + MouseCapture(WinEvent, Window, DateTime), + Manage(Window, DateTime), + Unmanage(Window, DateTime), + Raise(Window, DateTime), + TitleUpdate(WinEvent, Window, DateTime), } impl Display for WindowManagerEvent { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::Manage(window) => { + Self::Manage(window, _) => { write!(f, "Manage (Window: {window})") } - Self::Unmanage(window) => { + Self::Unmanage(window, _) => { write!(f, "Unmanage (Window: {window})") } - Self::Destroy(winevent, window) => { + Self::Destroy(winevent, window, _) => { write!(f, "Destroy (WinEvent: {winevent}, Window: {window})") } - Self::FocusChange(winevent, window) => { + Self::FocusChange(winevent, window, _) => { write!(f, "FocusChange (WinEvent: {winevent}, Window: {window})",) } - Self::Hide(winevent, window) => { + Self::Hide(winevent, window, _) => { write!(f, "Hide (WinEvent: {winevent}, Window: {window})") } - Self::Cloak(winevent, window) => { + Self::Cloak(winevent, window, _) => { write!(f, "Cloak (WinEvent: {winevent}, Window: {window})") } - Self::Minimize(winevent, window) => { + Self::Minimize(winevent, window, _) => { write!(f, "Minimize (WinEvent: {winevent}, Window: {window})") } - Self::Show(winevent, window) => { + Self::Show(winevent, window, _) => { write!(f, "Show (WinEvent: {winevent}, Window: {window})") } - Self::Uncloak(winevent, window) => { + Self::Uncloak(winevent, window, _) => { write!(f, "Uncloak (WinEvent: {winevent}, Window: {window})") } - Self::MoveResizeStart(winevent, window) => { + Self::MoveResizeStart(winevent, window, _) => { write!( f, "MoveResizeStart (WinEvent: {winevent}, Window: {window})", ) } - Self::MoveResizeEnd(winevent, window) => { + Self::MoveResizeEnd(winevent, window, _) => { write!(f, "MoveResizeEnd (WinEvent: {winevent}, Window: {window})",) } - Self::MouseCapture(winevent, window) => { + Self::MouseCapture(winevent, window, _) => { write!(f, "MouseCapture (WinEvent: {winevent}, Window: {window})",) } - Self::Raise(window) => { + Self::Raise(window, _) => { write!(f, "Raise (Window: {window})") } - Self::TitleUpdate(winevent, window) => { + Self::TitleUpdate(winevent, window, _) => { write!(f, "TitleUpdate (WinEvent: {winevent}, Window: {window})") } } @@ -83,47 +85,75 @@ impl Display for WindowManagerEvent { } impl WindowManagerEvent { + pub const fn timestamp(self) -> DateTime { + match self { + WindowManagerEvent::Destroy(_, _, timestamp) + | WindowManagerEvent::FocusChange(_, _, timestamp) + | WindowManagerEvent::Hide(_, _, timestamp) + | WindowManagerEvent::Cloak(_, _, timestamp) + | WindowManagerEvent::Minimize(_, _, timestamp) + | WindowManagerEvent::Show(_, _, timestamp) + | WindowManagerEvent::Uncloak(_, _, timestamp) + | WindowManagerEvent::MoveResizeStart(_, _, timestamp) + | WindowManagerEvent::MoveResizeEnd(_, _, timestamp) + | WindowManagerEvent::MouseCapture(_, _, timestamp) + | WindowManagerEvent::Manage(_, timestamp) + | WindowManagerEvent::Unmanage(_, timestamp) + | WindowManagerEvent::Raise(_, timestamp) + | WindowManagerEvent::TitleUpdate(_, _, timestamp) => timestamp, + } + } pub const fn window(self) -> Window { match self { - Self::Destroy(_, window) - | Self::FocusChange(_, window) - | Self::Hide(_, window) - | Self::Cloak(_, window) - | Self::Minimize(_, window) - | Self::Show(_, window) - | Self::Uncloak(_, window) - | Self::MoveResizeStart(_, window) - | Self::MoveResizeEnd(_, window) - | Self::MouseCapture(_, window) - | Self::Raise(window) - | Self::Manage(window) - | Self::Unmanage(window) - | Self::TitleUpdate(_, window) => window, + Self::Destroy(_, window, _) + | Self::FocusChange(_, window, _) + | Self::Hide(_, window, _) + | Self::Cloak(_, window, _) + | Self::Minimize(_, window, _) + | Self::Show(_, window, _) + | Self::Uncloak(_, window, _) + | Self::MoveResizeStart(_, window, _) + | Self::MoveResizeEnd(_, window, _) + | Self::MouseCapture(_, window, _) + | Self::Raise(window, _) + | Self::Manage(window, _) + | Self::Unmanage(window, _) + | Self::TitleUpdate(_, window, _) => window, } } - pub fn from_win_event(winevent: WinEvent, window: Window) -> Option { + pub fn from_win_event( + winevent: WinEvent, + window: Window, + timestamp: DateTime, + ) -> Option { match winevent { - WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)), + WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window, timestamp)), - WinEvent::ObjectHide => Option::from(Self::Hide(winevent, window)), - WinEvent::ObjectCloaked => Option::from(Self::Cloak(winevent, window)), + WinEvent::ObjectHide => Option::from(Self::Hide(winevent, window, timestamp)), + WinEvent::ObjectCloaked => Option::from(Self::Cloak(winevent, window, timestamp)), - WinEvent::SystemMinimizeStart => Option::from(Self::Minimize(winevent, window)), + WinEvent::SystemMinimizeStart => { + Option::from(Self::Minimize(winevent, window, timestamp)) + } WinEvent::ObjectShow | WinEvent::SystemMinimizeEnd => { - Option::from(Self::Show(winevent, window)) + Option::from(Self::Show(winevent, window, timestamp)) } - WinEvent::ObjectUncloaked => Option::from(Self::Uncloak(winevent, window)), + WinEvent::ObjectUncloaked => Option::from(Self::Uncloak(winevent, window, timestamp)), WinEvent::ObjectFocus | WinEvent::SystemForeground => { - Option::from(Self::FocusChange(winevent, window)) + Option::from(Self::FocusChange(winevent, window, timestamp)) + } + WinEvent::SystemMoveSizeStart => { + Option::from(Self::MoveResizeStart(winevent, window, timestamp)) + } + WinEvent::SystemMoveSizeEnd => { + Option::from(Self::MoveResizeEnd(winevent, window, timestamp)) } - WinEvent::SystemMoveSizeStart => Option::from(Self::MoveResizeStart(winevent, window)), - WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)), WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => { - Option::from(Self::MouseCapture(winevent, window)) + Option::from(Self::MouseCapture(winevent, window, timestamp)) } WinEvent::ObjectNameChange => { // Some apps like Firefox don't send ObjectCreate or ObjectShow on launch @@ -152,9 +182,9 @@ impl WindowManagerEvent { .is_some(); if should_trigger_show { - Option::from(Self::Show(winevent, window)) + Option::from(Self::Show(winevent, window, timestamp)) } else { - Option::from(Self::TitleUpdate(winevent, window)) + Option::from(Self::TitleUpdate(winevent, window, timestamp)) } } _ => None, diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index e044e6d0..cee4f200 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -1061,4 +1061,8 @@ impl WindowsApi { pub fn wts_register_session_notification(hwnd: isize) -> Result<()> { unsafe { WTSRegisterSessionNotification(HWND(hwnd), 1) }.process() } + + pub fn tick_count() -> u64 { + unsafe { windows::Win32::System::SystemInformation::GetTickCount64() } + } } diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 14c8e5a3..a097afa5 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -1,4 +1,9 @@ +use chrono::TimeZone; +use chrono::Utc; use std::collections::VecDeque; +use std::ops::Sub; +use std::time::Duration; +use std::time::UNIX_EPOCH; use windows::Win32::Foundation::BOOL; use windows::Win32::Foundation::HWND; @@ -67,13 +72,25 @@ pub extern "system" fn win_event_hook( id_object: i32, _id_child: i32, _id_event_thread: u32, - _dwms_event_time: u32, + dwms_event_time: u32, ) { // OBJID_WINDOW if id_object != 0 { return; } + let millis_since_boot = WindowsApi::tick_count(); + let system_time_now = std::time::SystemTime::now(); + + let boot_time = system_time_now + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .sub(Duration::from_millis(millis_since_boot)) + .as_secs(); + + let boot_time_utc = Utc.timestamp_opt(boot_time as i64, 0).unwrap(); + let timestamp = boot_time_utc + Duration::from_millis(dwms_event_time as u64); + let window = Window::from(hwnd); let winevent = match WinEvent::try_from(event) { @@ -81,7 +98,7 @@ pub extern "system" fn win_event_hook( Err(_) => return, }; - let event_type = match WindowManagerEvent::from_win_event(winevent, window) { + let event_type = match WindowManagerEvent::from_win_event(winevent, window, timestamp) { None => { tracing::trace!( "Unhandled WinEvent: {winevent} (hwnd: {}, exe: {}, title: {}, class: {})",