mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-29 13:41:56 +02:00
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:
78
komorebi/src/hidden.rs
Normal file
78
komorebi/src/hidden.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)))?));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user