fix(wm): listen to displaychange w/ hidden hwnd

This commit removes the previous polling strategy on ObjectCreate events
and uses a hidden window to listen to WM_DISPLAYCHANGE.

Unfortunately, as all monitors change HMONITOR values on monitor
attach/detach, even if the monitor remains attached, the only real
choice we have when a monitor which previously held windows is detached
is to read the entire monitor and workspace state again, as we do when
we initialise the window manager for the first time.

Since it's possible that the "wrong" monitor in the state has its
HMONITOR value updated, we also have to load the configuration again.

This commit deprecates WindowManagerEvent::MonitorPoll.

fix #267
This commit is contained in:
LGUG2Z
2022-10-20 13:54:34 -07:00
committed by جاد
parent 438bfc86ff
commit 75d72522a2
8 changed files with 161 additions and 24 deletions

78
komorebi/src/hidden.rs Normal file
View File

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

View File

@@ -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,
)))?));

View File

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

View File

@@ -357,7 +357,7 @@ impl Window {
#[tracing::instrument(fields(exe, title))]
pub fn should_manage(self, event: Option<WindowManagerEvent>) -> Result<bool> {
if let Some(WindowManagerEvent::MonitorPoll(_, _)) = event {
if let Some(WindowManagerEvent::DisplayChange(_)) = event {
return Ok(true);
}

View File

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

View File

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

View File

@@ -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<isize> {
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()

View File

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