diff --git a/Cargo.lock b/Cargo.lock index 46e3c1ba..e4134f02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,9 +1074,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1c7f11b289e450f78d55dd9dc09ae91c6ae8faed980bbf3e3a4c8f166ac259" +checksum = "68088239696c06152844eadc03d262f088932cce50c67e4ace86e19d95e976fe" dependencies = [ "const-sha1", "windows_gen", @@ -1085,15 +1085,15 @@ dependencies = [ [[package]] name = "windows_gen" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f5facfb04bc84b5fcd27018266d90ce272e11f8b91745dfdd47282e8e0607e" +checksum = "cf583322dc423ee021035b358e535015f7fd163058a31e2d37b99a939141121d" [[package]] name = "windows_macros" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c32753c378262520a4fa70c2e4389f4649e751faab2a887090567cff192d299" +checksum = "58acfb8832e9f707f8997bd161e537a1c1f603e60a5bd9c3cf53484fdcc998f3" dependencies = [ "syn", "windows_gen", diff --git a/bindings/Cargo.toml b/bindings/Cargo.toml index 2e995854..d2550407 100644 --- a/bindings/Cargo.toml +++ b/bindings/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -windows = "0.17.2" +windows = "0.18" [build-dependencies] -windows = "0.17.2" \ No newline at end of file +windows = "0.18" \ No newline at end of file diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index ab0fc545..d7b8c34c 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -1,6 +1,6 @@ use bindings::Windows::Win32::Foundation::RECT; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct Rect { pub left: i32, pub top: i32, diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index c494a698..f2ad7e9c 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -24,6 +24,7 @@ mod monitor; mod process_command; mod process_event; mod ring; +mod set_window_position; mod styles; mod window; mod window_manager; @@ -117,6 +118,7 @@ fn main() -> Result<()> { tracing::error!( "received ctrl-c, restoring all hidden windows and terminating process" ); + wm.lock().unwrap().restore_all_windows(); std::process::exit(130); } diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index 81c2fba8..b26f844b 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -30,15 +30,17 @@ pub fn new(id: isize, monitor_size: Rect, work_area_size: Rect) -> Monitor { } impl Monitor { - pub fn load_focused_workspace(&mut self) { + pub fn load_focused_workspace(&mut self) -> Result<()> { let focused_idx = self.focused_workspace_idx(); for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() { if i == focused_idx { - workspace.restore(); + workspace.restore()?; } else { workspace.hide(); } } + + Ok(()) } pub fn add_container(&mut self, container: Container) -> Result<()> { @@ -121,7 +123,7 @@ impl Monitor { pub fn update_focused_workspace(&mut self) -> Result<()> { tracing::info!("updating workspace: {}", self.focused_workspace_idx()); - let work_area = self.work_area_size().clone(); + let work_area = *self.work_area_size(); self.focused_workspace_mut() .context("there is no workspace")? diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 6933411d..660d6c1e 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -116,7 +116,7 @@ impl WindowManager { } SocketMessage::Retile => { for monitor in self.monitors_mut() { - let work_area = monitor.work_area_size().clone(); + let work_area = *monitor.work_area_size(); monitor .focused_workspace_mut() .context("there is no workspace")? diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index eebde1de..c5f3f715 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -52,7 +52,7 @@ impl WindowManager { } for (i, monitor) in self.monitors_mut().iter_mut().enumerate() { - let work_area = monitor.work_area_size().clone(); + let work_area = *monitor.work_area_size(); for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() { let reaped_orphans = workspace.reap_orphans()?; if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 { @@ -89,9 +89,19 @@ impl WindowManager { self.update_focused_workspace(false)?; } } - WindowManagerEvent::FocusChange(_, window) => self - .focused_workspace_mut()? - .focus_container_by_window(window.hwnd)?, + WindowManagerEvent::FocusChange(_, window) => { + let workspace = self.focused_workspace_mut()?; + if workspace + .floating_windows() + .iter() + .any(|w| w.hwnd == window.hwnd) + { + return Ok(()); + } + + self.focused_workspace_mut()? + .focus_container_by_window(window.hwnd)?; + } WindowManagerEvent::Show(_, window) => { let workspace = self.focused_workspace_mut()?; @@ -103,8 +113,16 @@ impl WindowManager { WindowManagerEvent::MoveResizeStart(_, _window) => { // TODO: Implement dragging resize (one day) } - WindowManagerEvent::MoveResizeEnd(_, _window) => { + WindowManagerEvent::MoveResizeEnd(_, window) => { let workspace = self.focused_workspace_mut()?; + if workspace + .floating_windows() + .iter() + .any(|w| w.hwnd == window.hwnd) + { + return Ok(()); + } + let focused_idx = workspace.focused_container_idx(); match workspace.container_idx_from_current_point() { diff --git a/komorebi/src/set_window_position.rs b/komorebi/src/set_window_position.rs new file mode 100644 index 00000000..c0e6110c --- /dev/null +++ b/komorebi/src/set_window_position.rs @@ -0,0 +1,38 @@ +use bitflags::bitflags; + +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW; + +bitflags! { + #[derive(Default)] + pub struct SetWindowPosition: u32 { + const ASYNC_WINDOW_POS = SWP_ASYNCWINDOWPOS.0; + const DEFER_ERASE = SWP_DEFERERASE.0; + const DRAW_FRAME = SWP_DRAWFRAME.0; + const FRAME_CHANGED = SWP_FRAMECHANGED.0; + const HIDE_WINDOW = SWP_HIDEWINDOW.0; + const NO_ACTIVATE = SWP_NOACTIVATE.0; + const NO_COPY_BITS = SWP_NOCOPYBITS.0; + const NO_MOVE = SWP_NOMOVE.0; + const NO_OWNER_Z_ORDER = SWP_NOOWNERZORDER.0; + const NO_REDRAW = SWP_NOREDRAW.0; + const NO_REPOSITION = SWP_NOREPOSITION.0; + const NO_SEND_CHANGING = SWP_NOSENDCHANGING.0; + const NO_SIZE = SWP_NOSIZE.0; + const NO_Z_ORDER = SWP_NOZORDER.0; + const SHOW_WINDOW = SWP_SHOWWINDOW.0; + } +} diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index d3c3bc7a..141c69ba 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -50,8 +50,42 @@ impl Window { HWND(self.hwnd) } - pub fn set_position(&mut self, layout: &Rect) -> Result<()> { - WindowsApi::set_window_pos(self.hwnd(), layout) + pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> { + // NOTE: This is how the border variable below was calculated; every time this code was + // run on any window in any position, the generated border was always the same, so I am + // hard coding the border Rect to avoid two calls to set_window_pos and making the screen + // flicker on container/window movement. Still not 100% sure if this is DPI-aware. + + // Set the new position first to be able to get the extended frame bounds + // WindowsApi::set_window_pos(self.hwnd(), layout, false, false)?; + // let mut rect = WindowsApi::window_rect(self.hwnd())?; + + // Get the extended frame bounds of the new position + // let frame = WindowsApi::window_rect_with_extended_frame_bounds(self.hwnd())?; + + // Calculate the invisible border diff + // let border = Rect { + // left: frame.left - rect.left, + // top: frame.top - rect.top, + // right: rect.right - frame.right, + // bottom: rect.bottom - frame.bottom, + // }; + + let mut rect = *layout; + let border = Rect { + left: 12, + top: 0, + right: 24, + bottom: 12, + }; + + // Remove the invisible border + rect.left -= border.left; + rect.top -= border.top; + rect.right += border.right; + rect.bottom += border.bottom; + + WindowsApi::position_window(self.hwnd(), &rect, top) } pub fn hide(&self) { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 96873cb4..8cbe423e 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -106,7 +106,7 @@ impl WindowManager { .context("there is no monitor")?; target_monitor.add_container(container)?; - target_monitor.load_focused_workspace(); + target_monitor.load_focused_workspace()?; if follow { self.focus_monitor(idx)?; @@ -118,7 +118,7 @@ impl WindowManager { pub fn move_container_to_workspace(&mut self, idx: usize, follow: bool) -> Result<()> { let monitor = self.focused_monitor_mut().context("there is no monitor")?; monitor.move_container_to_workspace(idx, follow)?; - monitor.load_focused_workspace(); + monitor.load_focused_workspace()?; self.update_focused_workspace(true) } @@ -210,7 +210,7 @@ impl WindowManager { } pub fn toggle_float(&mut self) -> Result<()> { - let hwnd = WindowsApi::foreground_window()?; + let hwnd = WindowsApi::top_visible_window()?; let workspace = self.focused_workspace_mut()?; let mut is_floating_window = false; @@ -222,12 +222,14 @@ impl WindowManager { } if is_floating_window { + tracing::info!("unfloating window"); self.unfloat_window()?; + self.update_focused_workspace(true) } else { + tracing::info!("floating window"); self.float_window()?; + self.update_focused_workspace(false) } - - self.update_focused_workspace(true) } pub fn float_window(&mut self) -> Result<()> { @@ -251,7 +253,7 @@ impl WindowManager { bottom: half_weight, }; - window.set_position(¢er)?; + window.set_position(¢er, true)?; window.focus()?; Ok(()) @@ -270,7 +272,7 @@ impl WindowManager { Some(_) => self.monocle_off()?, } - self.update_focused_workspace(true) + self.update_focused_workspace(false) } pub fn monocle_on(&mut self) -> Result<()> { @@ -366,7 +368,7 @@ impl WindowManager { .get_mut(monitor_idx) .context("there is no monitor")?; - let work_area = monitor.work_area_size().clone(); + let work_area = *monitor.work_area_size(); let focused_workspace_idx = monitor.focused_workspace_idx(); let workspace = monitor @@ -472,11 +474,10 @@ impl WindowManager { } pub fn focused_monitor_work_area(&self) -> Result { - Ok(self + Ok(*self .focused_monitor() .context("there is no monitor")? - .work_area_size() - .clone()) + .work_area_size()) } pub fn focus_monitor(&mut self, idx: usize) -> Result<()> { @@ -521,7 +522,7 @@ impl WindowManager { .context("there is no workspace")?; monitor.focus_workspace(idx)?; - monitor.load_focused_workspace(); + monitor.load_focused_workspace()?; self.update_focused_workspace(true) } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index a7cedc36..0e9ffb11 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -12,17 +12,22 @@ use bindings::Windows::Win32::Foundation::HWND; use bindings::Windows::Win32::Foundation::LPARAM; use bindings::Windows::Win32::Foundation::POINT; use bindings::Windows::Win32::Foundation::PWSTR; +use bindings::Windows::Win32::Foundation::RECT; use bindings::Windows::Win32::Graphics::Dwm::DwmGetWindowAttribute; use bindings::Windows::Win32::Graphics::Dwm::DWMWA_CLOAKED; +use bindings::Windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS; +use bindings::Windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE; use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP; use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED; use bindings::Windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL; use bindings::Windows::Win32::Graphics::Gdi::EnumDisplayMonitors; +use bindings::Windows::Win32::Graphics::Gdi::GetMonitorInfoW; use bindings::Windows::Win32::Graphics::Gdi::MonitorFromWindow; use bindings::Windows::Win32::Graphics::Gdi::HDC; +use bindings::Windows::Win32::Graphics::Gdi::HMONITOR; use bindings::Windows::Win32::Graphics::Gdi::MONITORENUMPROC; +use bindings::Windows::Win32::Graphics::Gdi::MONITORINFO; use bindings::Windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST; -use bindings::Windows::Win32::Graphics::Gdi::{GetMonitorInfoW, HMONITOR, MONITORINFO}; use bindings::Windows::Win32::System::Threading::AttachThreadInput; use bindings::Windows::Win32::System::Threading::GetCurrentProcessId; use bindings::Windows::Win32::System::Threading::GetCurrentThreadId; @@ -35,7 +40,8 @@ use bindings::Windows::Win32::UI::KeyboardAndMouseInput::SetFocus; use bindings::Windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::EnumWindows; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetCursorPos; -use bindings::Windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; +use bindings::Windows::Win32::UI::WindowsAndMessaging::GetTopWindow; +use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowRect; use bindings::Windows::Win32::UI::WindowsAndMessaging::GetWindowTextW; @@ -51,9 +57,11 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::SetWindowPos; use bindings::Windows::Win32::UI::WindowsAndMessaging::ShowWindow; use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use bindings::Windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; +use bindings::Windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST; +use bindings::Windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST; +use bindings::Windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS; use bindings::Windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD; -use bindings::Windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE; use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_HIDE; use bindings::Windows::Win32::UI::WindowsAndMessaging::SW_RESTORE; use bindings::Windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX; @@ -61,10 +69,12 @@ use bindings::Windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; use komorebi_core::Rect; use crate::container::Container; +use crate::monitor; use crate::monitor::Monitor; use crate::ring::Ring; +use crate::set_window_position::SetWindowPosition; +use crate::windows_callbacks; use crate::workspace::Workspace; -use crate::{monitor, windows_callbacks}; pub enum WindowsResult { Err(E), @@ -211,16 +221,23 @@ impl WindowsApi { unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) }.0 } - pub fn set_window_pos(hwnd: HWND, layout: &Rect) -> Result<()> { + pub fn position_window(hwnd: HWND, layout: &Rect, top: bool) -> Result<()> { + let flags = SetWindowPosition::NO_ACTIVATE; + + let position = if top { HWND_TOPMOST } else { HWND_NOTOPMOST }; + Self::set_window_pos(hwnd, layout, position, flags.bits()) + } + + pub fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> Result<()> { Result::from(WindowsResult::from(unsafe { SetWindowPos( hwnd, - HWND_NOTOPMOST, + position, layout.left, layout.top, layout.right, layout.bottom, - SWP_NOACTIVATE, + SET_WINDOW_POS_FLAGS(flags), ) })) } @@ -255,8 +272,29 @@ impl WindowsApi { } } - pub fn foreground_window() -> Result { - Result::from(WindowsResult::from(unsafe { GetForegroundWindow().0 })) + pub fn top_window() -> Result { + Result::from(WindowsResult::from(unsafe { GetTopWindow(HWND::NULL).0 })) + } + + pub fn next_window(hwnd: HWND) -> Result { + Result::from(WindowsResult::from(unsafe { + GetWindow(hwnd, GW_HWNDNEXT).0 + })) + } + + pub fn top_visible_window() -> Result { + let hwnd = Self::top_window()?; + let mut next_hwnd = hwnd; + + while next_hwnd != 0 { + if Self::is_window_visible(HWND(next_hwnd)) { + return Ok(next_hwnd); + } + + next_hwnd = Self::next_window(HWND(next_hwnd))?; + } + + Err(eyre::anyhow!("could not find next window")) } pub fn window_rect(hwnd: HWND) -> Result { @@ -417,18 +455,35 @@ impl WindowsApi { Ok(String::from_utf16(&class[0..len as usize])?) } - pub fn is_window_cloaked(hwnd: HWND) -> Result { - let mut cloaked: u32 = 0; - + pub fn dwm_get_window_attribute( + hwnd: HWND, + attribute: DWMWINDOWATTRIBUTE, + value: &mut T, + ) -> Result<()> { unsafe { DwmGetWindowAttribute( hwnd, - std::mem::transmute::<_, u32>(DWMWA_CLOAKED), - (&mut cloaked as *mut u32).cast(), - u32::try_from(std::mem::size_of::())?, + std::mem::transmute::<_, u32>(attribute), + (value as *mut T).cast(), + u32::try_from(std::mem::size_of::())?, )?; } + Ok(()) + } + + #[allow(dead_code)] + pub fn window_rect_with_extended_frame_bounds(hwnd: HWND) -> Result { + let mut rect = RECT::default(); + Self::dwm_get_window_attribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect)?; + + Ok(Rect::from(rect)) + } + + pub fn is_window_cloaked(hwnd: HWND) -> Result { + let mut cloaked: u32 = 0; + Self::dwm_get_window_attribute(hwnd, DWMWA_CLOAKED, &mut cloaked)?; + Ok(matches!( cloaked, DWM_CLOAKED_APP | DWM_CLOAKED_SHELL | DWM_CLOAKED_INHERITED diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 93f3e712..af6fcb8b 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -37,8 +37,8 @@ impl Default for Workspace { floating_windows: Vec::default(), layout: Layout::BSP, layout_flip: None, - workspace_padding: Option::from(20), - container_padding: Option::from(5), + workspace_padding: Option::from(10), + container_padding: Option::from(10), latest_layout: vec![], } } @@ -53,22 +53,28 @@ impl Workspace { } } - pub fn restore(&mut self) { - for container in self.containers_mut() { + pub fn restore(&mut self) -> Result<()> { + let idx = self.focused_container_idx(); + for (i, container) in self.containers_mut().iter_mut().enumerate() { if let Some(window) = container.visible_window_mut() { window.restore(); + + if idx == i { + window.focus()?; + } } } + + Ok(()) } pub fn update(&mut self, work_area: &Rect) -> Result<()> { - let mut adjusted_work_area = work_area.clone(); + let mut adjusted_work_area = *work_area; adjusted_work_area.add_padding(self.workspace_padding()); if let Some(container) = self.monocle_container_mut() { if let Some(window) = container.focused_window_mut() { - window.set_position(&adjusted_work_area)?; - window.focus()?; + window.set_position(&adjusted_work_area, true)?; } } else { let layouts = self.layout().calculate( @@ -81,7 +87,7 @@ impl Workspace { let windows = self.visible_windows_mut(); for (i, window) in windows.into_iter().enumerate() { if let (Some(window), Some(layout)) = (window, layouts.get(i)) { - window.set_position(layout)?; + window.set_position(layout, false)?; } } @@ -453,7 +459,7 @@ impl Workspace { } pub fn remove_focused_floating_window(&mut self) -> Option { - let hwnd = WindowsApi::foreground_window().ok()?; + let hwnd = WindowsApi::top_visible_window().ok()?; let mut idx = None; for (i, window) in self.floating_windows.iter().enumerate() {