diff --git a/Cargo.lock b/Cargo.lock index a1d043ec..486d7952 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,6 +530,7 @@ dependencies = [ "tracing-subscriber", "uds_windows", "which", + "winput", "winvd", ] @@ -1159,9 +1160,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" dependencies = [ "proc-macro2", "quote", @@ -1170,9 +1171,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ef9905d4f98c059046037078f070e66de0f5ac69e5bb63f6bf805b570b3b51" +checksum = "92d77883450d697c0010e60db3d940ed130b0ed81d27485edee981621b434e52" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -1493,6 +1494,15 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d5cf83fb08083438c5c46723e6206b2970da57ce314f80b57724439aaacab" +[[package]] +name = "winput" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4bec39938e0ae68b300e2a4197b6437f13d53d1c146c6e297e346a71d5dde9" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winvd" version = "0.0.20" diff --git a/komorebi/Cargo.toml b/komorebi/Cargo.toml index ffc97a27..ca16b1a8 100644 --- a/komorebi/Cargo.toml +++ b/komorebi/Cargo.toml @@ -30,6 +30,7 @@ tracing-appender = "0.1" tracing-subscriber = "0.2" uds_windows = "1" which = "4" +winput = "0.2" winvd = "0.0.20" [features] diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index ec555895..dd772cb0 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -44,6 +44,18 @@ impl Container { } } + pub fn hwnd_from_exe(&self, exe: &str) -> Option { + for window in self.windows() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Option::from(window.hwnd); + } + } + } + + None + } + pub fn contains_window(&self, hwnd: isize) -> bool { for window in self.windows() { if window.hwnd == hwnd { diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 1d8917a1..7a20fa8a 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -25,6 +25,7 @@ use which::which; use crate::process_command::listen_for_commands; use crate::process_event::listen_for_events; +use crate::process_movement::listen_for_movements; use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; @@ -36,6 +37,7 @@ mod container; mod monitor; mod process_command; mod process_event; +mod process_movement; mod set_window_position; mod styles; mod window; @@ -227,6 +229,7 @@ fn main() -> Result<()> { wm.lock().init()?; listen_for_commands(wm.clone()); listen_for_events(wm.clone()); + listen_for_movements(wm.clone()); load_configuration()?; diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 2e655e55..df2447b2 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -16,7 +16,6 @@ use komorebi_core::StateQuery; use crate::window_manager; use crate::window_manager::WindowManager; -use crate::windows_api::WindowsApi; use crate::FLOAT_IDENTIFIERS; use crate::MANAGE_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_CLASSES; @@ -214,18 +213,12 @@ impl WindowManager { } SocketMessage::FocusFollowsMouse(enable) => { if enable { - WindowsApi::enable_focus_follows_mouse()?; + self.autoraise = true; } else { - WindowsApi::disable_focus_follows_mouse()?; - } - } - SocketMessage::ToggleFocusFollowsMouse => { - if WindowsApi::focus_follows_mouse()? { - WindowsApi::disable_focus_follows_mouse()?; - } else { - WindowsApi::enable_focus_follows_mouse()?; + self.autoraise = false; } } + SocketMessage::ToggleFocusFollowsMouse => self.autoraise = !self.autoraise, SocketMessage::ReloadConfiguration => { Self::reload_configuration(); } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index b97eabfa..4a493506 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -91,6 +91,10 @@ impl WindowManager { } match event { + WindowManagerEvent::Raise(window) => { + window.raise()?; + self.has_pending_raise_op = false; + } WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => { @@ -315,6 +319,7 @@ impl WindowManager { .open(hwnd_json)?; serde_json::to_writer_pretty(&file, &known_hwnds)?; + tracing::info!("processed: {}", event.window().to_string()); Ok(()) } diff --git a/komorebi/src/process_movement.rs b/komorebi/src/process_movement.rs new file mode 100644 index 00000000..0cadf234 --- /dev/null +++ b/komorebi/src/process_movement.rs @@ -0,0 +1,36 @@ +use std::sync::Arc; + +use parking_lot::Mutex; +use winput::message_loop; +use winput::message_loop::Event; +use winput::Action; + +use crate::window_manager::WindowManager; + +#[tracing::instrument] +pub fn listen_for_movements(wm: Arc>) { + std::thread::spawn(move || { + let mut ignore_movement = false; + + let receiver = message_loop::start().expect("could not start winput message loop"); + + loop { + match receiver.next_event() { + // Don't want to send any raise events while we are dragging or resizing + Event::MouseButton { action, .. } => match action { + Action::Press => ignore_movement = true, + Action::Release => ignore_movement = false, + }, + Event::MouseMoveRelative { .. } => { + if !ignore_movement { + match wm.lock().raise_window_at_cursor_pos() { + Ok(_) => {} + Err(error) => tracing::error!("{}", error), + } + } + } + _ => {} + } + } + }); +} diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 4e877991..4868333a 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -156,6 +156,27 @@ impl Window { WindowsApi::maximize_window(self.hwnd()); } + pub fn raise(self) -> Result<()> { + // Attach komorebi thread to Window thread + let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd()); + let current_thread_id = WindowsApi::current_thread_id(); + WindowsApi::attach_thread_input(current_thread_id, window_thread_id, true)?; + + // Raise Window to foreground + match WindowsApi::set_foreground_window(self.hwnd()) { + Ok(_) => {} + Err(error) => { + tracing::error!( + "could not set as foreground window, but continuing execution of focus(): {}", + error + ); + } + }; + + // This isn't really needed when the above command works as expected via AHK + WindowsApi::set_focus(self.hwnd()) + } + pub fn focus(self) -> Result<()> { // Attach komorebi thread to Window thread let (_, window_thread_id) = WindowsApi::window_thread_process_id(self.hwnd()); diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 86f3c8ee..f5da1244 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -44,14 +44,17 @@ pub struct WindowManager { pub incoming_events: Arc>>, pub command_listener: UnixListener, pub is_paused: bool, + pub autoraise: bool, pub hotwatch: Hotwatch, pub virtual_desktop_id: Option, + pub has_pending_raise_op: bool, } #[derive(Debug, Serialize)] pub struct State { pub monitors: Ring, pub is_paused: bool, + pub autoraise: bool, pub float_identifiers: Vec, pub manage_identifiers: Vec, pub layered_exe_whitelist: Vec, @@ -65,6 +68,7 @@ impl From<&mut WindowManager> for State { Self { monitors: wm.monitors.clone(), is_paused: wm.is_paused, + autoraise: wm.autoraise, float_identifiers: FLOAT_IDENTIFIERS.lock().clone(), manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(), layered_exe_whitelist: LAYERED_EXE_WHITELIST.lock().clone(), @@ -128,8 +132,10 @@ impl WindowManager { incoming_events: incoming, command_listener: listener, is_paused: false, + autoraise: false, hotwatch: Hotwatch::new()?, virtual_desktop_id, + has_pending_raise_op: false, }) } @@ -360,6 +366,51 @@ impl WindowManager { Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?) } + #[tracing::instrument(skip(self))] + pub fn raise_window_at_cursor_pos(&mut self) -> Result<()> { + if !self.autoraise { + return Ok(()); + } + + if self.has_pending_raise_op { + Ok(()) + } else { + let mut hwnd = WindowsApi::window_at_cursor_pos()?; + let mut known_hwnd = false; + for monitor in self.monitors() { + for workspace in monitor.workspaces() { + if workspace.contains_window(hwnd) { + known_hwnd = true; + } + } + } + + if !known_hwnd { + let class = Window { hwnd }.class()?; + // Just Chromium and Electron fucking up everything, again + if class == *"Chrome_RenderWidgetHostHWND" { + for monitor in self.monitors() { + for workspace in monitor.workspaces() { + if let Some(exe_hwnd) = workspace.hwnd_from_exe(&Window { hwnd }.exe()?) + { + hwnd = exe_hwnd; + known_hwnd = true; + } + } + } + } + } + + if known_hwnd && self.focused_window()?.hwnd != hwnd { + let event = WindowManagerEvent::Raise(Window { hwnd }); + self.has_pending_raise_op = true; + Ok(WINEVENT_CALLBACK_CHANNEL.lock().0.send(event)?) + } else { + Ok(()) + } + } + } + #[tracing::instrument(skip(self))] pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> { tracing::info!("updating"); @@ -1080,6 +1131,12 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no container")) } + pub fn focused_window(&self) -> Result<&Window> { + self.focused_container()? + .focused_window() + .ok_or_else(|| anyhow!("there is no window")) + } + fn focused_window_mut(&mut self) -> Result<&mut Window> { self.focused_container_mut()? .focused_window_mut() diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index e97c5d78..1547fe9b 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -16,6 +16,7 @@ pub enum WindowManagerEvent { MouseCapture(WinEvent, Window), Manage(Window), Unmanage(Window), + Raise(Window), } impl Display for WindowManagerEvent { @@ -60,6 +61,9 @@ impl Display for WindowManagerEvent { winevent, window ) } + WindowManagerEvent::Raise(window) => { + write!(f, "Raise (Window: {})", window) + } } } } @@ -74,6 +78,7 @@ impl WindowManagerEvent { | WindowManagerEvent::Show(_, window) | WindowManagerEvent::MoveResizeEnd(_, window) | WindowManagerEvent::MouseCapture(_, window) + | WindowManagerEvent::Raise(window) | WindowManagerEvent::Manage(window) | WindowManagerEvent::Unmanage(window) => window, } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 7e5ffcd9..a688756a 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -60,6 +60,7 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW; use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos; use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW; +use bindings::Windows::Win32::UI::WindowsAndMessaging::WindowFromPoint; use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use bindings::Windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; @@ -349,6 +350,14 @@ impl WindowsApi { Ok(cursor_pos) } + pub fn window_from_point(point: POINT) -> Result { + Result::from(WindowsResult::from(unsafe { WindowFromPoint(point) })) + } + + pub fn window_at_cursor_pos() -> Result { + Self::window_from_point(Self::cursor_pos()?) + } + pub fn center_cursor_in_rect(rect: &Rect) -> Result<()> { Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2)) } @@ -554,6 +563,7 @@ impl WindowsApi { )) } + #[allow(dead_code)] pub fn system_parameters_info_w( action: SYSTEM_PARAMETERS_INFO_ACTION, ui_param: u32, @@ -565,6 +575,7 @@ impl WindowsApi { })) } + #[allow(dead_code)] pub fn focus_follows_mouse() -> Result { let mut is_enabled: BOOL = unsafe { std::mem::zeroed() }; @@ -578,6 +589,7 @@ impl WindowsApi { Ok(is_enabled.into()) } + #[allow(dead_code)] pub fn enable_focus_follows_mouse() -> Result<()> { Self::system_parameters_info_w( SPI_SETACTIVEWINDOWTRACKING, @@ -587,6 +599,7 @@ impl WindowsApi { ) } + #[allow(dead_code)] pub fn disable_focus_follows_mouse() -> Result<()> { Self::system_parameters_info_w( SPI_SETACTIVEWINDOWTRACKING, diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 32d07172..b23f3cd0 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -263,6 +263,38 @@ impl Workspace { idx } + pub fn hwnd_from_exe(&self, exe: &str) -> Option { + for container in self.containers() { + if let Some(hwnd) = container.hwnd_from_exe(exe) { + return Option::from(hwnd); + } + } + + if let Some(window) = self.maximized_window() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Option::from(window.hwnd); + } + } + } + + if let Some(container) = self.monocle_container() { + if let Some(hwnd) = container.hwnd_from_exe(exe) { + return Option::from(hwnd); + } + } + + for window in self.floating_windows() { + if let Ok(window_exe) = window.exe() { + if exe == window_exe { + return Option::from(window.hwnd); + } + } + } + + None + } + pub fn contains_window(&self, hwnd: isize) -> bool { for container in self.containers() { if container.contains_window(hwnd) {