Compare commits

...

1 Commits

Author SHA1 Message Date
LGUG2Z cf7108de94 think this still leaves us with inconsistent state 2024-06-19 11:28:16 -07:00
9 changed files with 146 additions and 79 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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"
]

View File

@ -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"] }

View File

@ -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> = 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 {

View File

@ -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;
}

View File

@ -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 {

View File

@ -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<Utc>),
FocusChange(WinEvent, Window, DateTime<Utc>),
Hide(WinEvent, Window, DateTime<Utc>),
Cloak(WinEvent, Window, DateTime<Utc>),
Minimize(WinEvent, Window, DateTime<Utc>),
Show(WinEvent, Window, DateTime<Utc>),
Uncloak(WinEvent, Window, DateTime<Utc>),
MoveResizeStart(WinEvent, Window, DateTime<Utc>),
MoveResizeEnd(WinEvent, Window, DateTime<Utc>),
MouseCapture(WinEvent, Window, DateTime<Utc>),
Manage(Window, DateTime<Utc>),
Unmanage(Window, DateTime<Utc>),
Raise(Window, DateTime<Utc>),
TitleUpdate(WinEvent, Window, DateTime<Utc>),
}
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<Utc> {
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<Self> {
pub fn from_win_event(
winevent: WinEvent,
window: Window,
timestamp: DateTime<Utc>,
) -> Option<Self> {
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,

View File

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

View File

@ -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: {})",