diff --git a/komorebi/src/hidden.rs b/komorebi/src/hidden.rs new file mode 100644 index 00000000..591f2b81 --- /dev/null +++ b/komorebi/src/hidden.rs @@ -0,0 +1,78 @@ +use std::sync::atomic::Ordering; +use std::time::Duration; + +use color_eyre::Result; +use windows::core::PCSTR; +use windows::Win32::Foundation::HWND; +use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA; +use windows::Win32::UI::WindowsAndMessaging::FindWindowA; +use windows::Win32::UI::WindowsAndMessaging::GetMessageA; +use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW; +use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; +use windows::Win32::UI::WindowsAndMessaging::MSG; +use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA; + +use crate::windows_callbacks; +use crate::WindowsApi; +use crate::HIDDEN_HWND; +use crate::TRANSPARENCY_COLOUR; + +#[derive(Debug, Clone, Copy)] +pub struct Hidden { + pub(crate) hwnd: isize, +} + +impl From for Hidden { + fn from(hwnd: isize) -> Self { + Self { hwnd } + } +} + +impl Hidden { + pub const fn hwnd(self) -> HWND { + HWND(self.hwnd) + } + + pub fn create(name: &str) -> Result<()> { + let name = format!("{name}\0"); + let instance = WindowsApi::module_handle_w()?; + let class_name = PCSTR(name.as_ptr()); + let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); + let window_class = WNDCLASSA { + hInstance: instance, + lpszClassName: class_name, + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(windows_callbacks::hidden_window), + hbrBackground: brush, + ..Default::default() + }; + + let _atom = WindowsApi::register_class_a(&window_class)?; + + let name_cl = name.clone(); + std::thread::spawn(move || -> Result<()> { + let hwnd = WindowsApi::create_hidden_window(PCSTR(name_cl.as_ptr()), instance)?; + let hidden = Self::from(hwnd); + + let mut message = MSG::default(); + + unsafe { + while GetMessageA(&mut message, hidden.hwnd(), 0, 0).into() { + DispatchMessageA(&message); + std::thread::sleep(Duration::from_millis(10)); + } + } + + Ok(()) + }); + + let mut hwnd = HWND(0); + while hwnd == HWND(0) { + hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) }; + } + + HIDDEN_HWND.store(hwnd.0, Ordering::SeqCst); + + Ok(()) + } +} diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 81070231..085a575d 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -38,6 +38,7 @@ use which::which; use winreg::enums::HKEY_CURRENT_USER; use winreg::RegKey; +use crate::hidden::Hidden; use komorebi_core::HidingBehaviour; use komorebi_core::Rect; use komorebi_core::SocketMessage; @@ -56,6 +57,7 @@ mod ring; mod border; mod container; +mod hidden; mod monitor; mod process_command; mod process_event; @@ -171,6 +173,8 @@ pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0); // 0 0 0 aka pure black, I doubt anyone will want this as a border colour pub const TRANSPARENCY_COLOUR: u32 = 0; +pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0); + fn setup() -> Result<(WorkerGuard, WorkerGuard)> { if std::env::var("RUST_LIB_BACKTRACE").is_err() { std::env::set_var("RUST_LIB_BACKTRACE", "1"); @@ -455,6 +459,8 @@ fn main() -> Result<()> { let winevent_listener = winevent_listener::new(Arc::new(Mutex::new(outgoing))); winevent_listener.start(); + Hidden::create("komorebi-hidden")?; + let wm = Arc::new(Mutex::new(WindowManager::new(Arc::new(Mutex::new( incoming, )))?)); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 56263573..60abc0dc 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -74,9 +74,9 @@ impl WindowManager { // Make sure we have the most recently focused monitor from any event match event { - WindowManagerEvent::MonitorPoll(_, window) - | WindowManagerEvent::FocusChange(_, window) + WindowManagerEvent::FocusChange(_, window) | WindowManagerEvent::Show(_, window) + | WindowManagerEvent::DisplayChange(window) | WindowManagerEvent::MoveResizeEnd(_, window) => { self.reconcile_monitors()?; @@ -482,7 +482,7 @@ impl WindowManager { } } } - WindowManagerEvent::MonitorPoll(..) | WindowManagerEvent::MouseCapture(..) => {} + WindowManagerEvent::DisplayChange(..) | WindowManagerEvent::MouseCapture(..) => {} }; if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index aaeaac99..2748a6b2 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -357,7 +357,7 @@ impl Window { #[tracing::instrument(fields(exe, title))] pub fn should_manage(self, event: Option) -> Result { - if let Some(WindowManagerEvent::MonitorPoll(_, _)) = event { + if let Some(WindowManagerEvent::DisplayChange(_)) = event { return Ok(true); } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index a4fc14dd..6b140b00 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -328,6 +328,8 @@ impl WindowManager { pub fn reconcile_monitors(&mut self) -> Result<()> { let valid_hmonitors = WindowsApi::valid_hmonitors()?; let mut invalid = vec![]; + let mut updated = vec![]; + let mut overlapping = vec![]; for monitor in self.monitors_mut() { if !valid_hmonitors.contains(&monitor.id()) { @@ -335,13 +337,22 @@ impl WindowManager { // If an invalid hmonitor has at least one window in the window manager state, // we can attempt to update its hmonitor id in-place so that it doesn't get reaped + // + // This needs to be done because when monitors are attached and detached, even + // monitors that remained connected get assigned new HMONITOR values if let Some(workspace) = monitor.focused_workspace() { if let Some(container) = workspace.focused_container() { if let Some(window) = container.focused_window() { let actual_hmonitor = WindowsApi::monitor_from_window(window.hwnd()); if actual_hmonitor != monitor.id() { monitor.set_id(actual_hmonitor); - mark_as_invalid = false; + + if updated.contains(&actual_hmonitor) { + overlapping.push(monitor.clone()); + } else { + mark_as_invalid = false; + updated.push(actual_hmonitor); + } } } } @@ -356,6 +367,17 @@ impl WindowManager { // Remove any invalid monitors from our state self.monitors_mut().retain(|m| !invalid.contains(&m.id())); + // If monitor IDs are overlapping we are fucked and need to load + // monitor and workspace state again, followed by the configuration + if !overlapping.is_empty() { + WindowsApi::load_monitor_information(&mut self.monitors)?; + WindowsApi::load_workspace_information(&mut self.monitors)?; + + std::thread::spawn(|| { + load_configuration().expect("could not load configuration"); + }); + } + let invisible_borders = self.invisible_borders; let offset = self.work_area_offset; diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index 96b1f7e1..62a045b4 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -22,7 +22,7 @@ pub enum WindowManagerEvent { Manage(Window), Unmanage(Window), Raise(Window), - MonitorPoll(WinEvent, Window), + DisplayChange(Window), } impl Display for WindowManagerEvent { @@ -77,12 +77,8 @@ impl Display for WindowManagerEvent { Self::Raise(window) => { write!(f, "Raise (Window: {})", window) } - Self::MonitorPoll(winevent, window) => { - write!( - f, - "MonitorPoll (WinEvent: {}, Window: {})", - winevent, window - ) + Self::DisplayChange(window) => { + write!(f, "DisplayChange (Window: {})", window) } } } @@ -99,9 +95,9 @@ impl WindowManagerEvent { | Self::MoveResizeStart(_, window) | Self::MoveResizeEnd(_, window) | Self::MouseCapture(_, window) - | Self::MonitorPoll(_, window) | Self::Raise(window) | Self::Manage(window) + | Self::DisplayChange(window) | Self::Unmanage(window) => window, } } @@ -147,17 +143,6 @@ impl WindowManagerEvent { None } } - WinEvent::ObjectCreate => { - if let Ok(title) = window.title() { - // Hidden COM support mechanism window that fires this event on both DPI/scaling - // changes and resolution changes, a good candidate for polling - if title == "OLEChannelWnd" { - return Option::from(Self::MonitorPoll(winevent, window)); - } - } - - None - } _ => None, } } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index b88f4a2c..c51df37d 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -104,7 +104,9 @@ use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX; use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA; use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; +use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED; use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED; +use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE; use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW; use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX; use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX; @@ -744,6 +746,26 @@ impl WindowsApi { .process() } + pub fn create_hidden_window(name: PCSTR, instance: HINSTANCE) -> Result { + unsafe { + CreateWindowExA( + WS_EX_NOACTIVATE, + name, + name, + WS_DISABLED, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + None, + None, + instance, + None, + ) + } + .process() + } + pub fn invalidate_border_rect() -> Result<()> { unsafe { InvalidateRect(HWND(BORDER_HWND.load(Ordering::SeqCst)), None, false) } .ok() diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 294ee11f..9803077a 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -22,6 +22,7 @@ use windows::Win32::UI::Accessibility::HWINEVENTHOOK; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; +use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE; use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; use crate::container::Container; @@ -158,3 +159,26 @@ pub extern "system" fn border_window( } } } + +pub extern "system" fn hidden_window( + window: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + unsafe { + match message as u32 { + WM_DISPLAYCHANGE => { + let event_type = WindowManagerEvent::DisplayChange(Window { hwnd: window.0 }); + WINEVENT_CALLBACK_CHANNEL + .lock() + .0 + .send(event_type) + .expect("could not send message on WINEVENT_CALLBACK_CHANNEL"); + + LRESULT(0) + } + _ => DefWindowProcW(window, message, wparam, lparam), + } + } +}