feat(wm): reconcile monitor state

When monitors turn on and off, they do not retain their hmonitor id,
therefore this commit introduces an initial attempt to reconcile invalid
and valid hmonitors after monitor changes based on the windows that are
assigned to them.

If a monitor has at least one window, and has been assigned a new
hmonitor id, komorebi will look up the current hmonitor of that window's
hwnd and update Monitor.id in-place.

When reconciling monitors, any monitor marked as invalid will be purged
from the window manager state.

This commit also applies some of the new clippy lints that come along
with the latest nightly release of Rust.

resolve #31
This commit is contained in:
LGUG2Z
2021-09-10 12:46:49 -07:00
parent e1bd0e9fcb
commit c4c8bd7d4b
7 changed files with 86 additions and 29 deletions

View File

@@ -2,7 +2,7 @@ use serde::Serialize;
use bindings::Windows::Win32::Foundation::RECT;
#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq)]
#[derive(Debug, Default, Clone, Copy, Serialize, Eq, PartialEq)]
pub struct Rect {
pub left: i32,
pub top: i32,
@@ -10,17 +10,6 @@ pub struct Rect {
pub bottom: i32,
}
impl Default for Rect {
fn default() -> Self {
Self {
left: 0,
top: 0,
right: 0,
bottom: 0,
}
}
}
impl From<RECT> for Rect {
fn from(rect: RECT) -> Self {
Self {

View File

@@ -6,6 +6,7 @@ use color_eyre::Result;
use getset::CopyGetters;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use serde::Serialize;
use komorebi_core::Rect;
@@ -14,9 +15,9 @@ use crate::container::Container;
use crate::ring::Ring;
use crate::workspace::Workspace;
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters)]
#[derive(Debug, Clone, Serialize, Getters, CopyGetters, MutGetters, Setters)]
pub struct Monitor {
#[getset(get_copy = "pub")]
#[getset(get_copy = "pub", set = "pub")]
id: isize,
monitor_size: Rect,
#[getset(get = "pub")]
@@ -30,11 +31,14 @@ pub struct Monitor {
impl_ring_elements!(Monitor, Workspace);
pub fn new(id: isize, monitor_size: Rect, work_area_size: Rect) -> Monitor {
let mut workspaces = Ring::default();
workspaces.elements_mut().push_back(Workspace::default());
Monitor {
id,
monitor_size,
work_area_size,
workspaces: Ring::default(),
workspaces,
workspace_names: HashMap::default(),
}
}

View File

@@ -55,8 +55,9 @@ impl WindowManager {
WindowManagerEvent::FocusChange(_, window)
| WindowManagerEvent::Show(_, window)
| WindowManagerEvent::MoveResizeEnd(_, window) => {
let monitor_idx = self
.monitor_idx_from_window(*window)
self.reconcile_monitors()?;
let monitor_idx = self.monitor_idx_from_window(*window)
.ok_or_else(|| anyhow!("there is no monitor associated with this window, it may have already been destroyed"))?;
self.focus_monitor(monitor_idx)?;

View File

@@ -224,6 +224,44 @@ impl WindowManager {
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn reconcile_monitors(&mut self) -> Result<()> {
let valid_hmonitors = WindowsApi::valid_hmonitors()?;
let mut invalid = vec![];
for monitor in self.monitors_mut() {
if !valid_hmonitors.contains(&monitor.id()) {
let mut mark_as_invalid = true;
// 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
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 mark_as_invalid {
invalid.push(monitor.id());
}
}
}
// Remove any invalid monitors from our state
self.monitors_mut().retain(|m| !invalid.contains(&m.id()));
// Check for and add any new monitors that may have been plugged in
WindowsApi::load_monitor_information(&mut self.monitors)?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn enforce_workspace_rules(&mut self) -> Result<()> {
let mut to_move = vec![];

View File

@@ -86,7 +86,6 @@ use crate::monitor::Monitor;
use crate::ring::Ring;
use crate::set_window_position::SetWindowPosition;
use crate::windows_callbacks;
use crate::workspace::Workspace;
pub enum WindowsResult<T, E> {
Err(E),
@@ -166,6 +165,17 @@ impl WindowsApi {
}))
}
pub fn valid_hmonitors() -> Result<Vec<isize>> {
let mut monitors: Vec<isize> = vec![];
let monitors_ref: &mut Vec<isize> = monitors.as_mut();
Self::enum_display_monitors(
windows_callbacks::valid_display_monitors,
monitors_ref as *mut Vec<isize> as isize,
)?;
Ok(monitors)
}
pub fn load_monitor_information(monitors: &mut Ring<Monitor>) -> Result<()> {
Self::enum_display_monitors(
windows_callbacks::enum_display_monitor,
@@ -181,9 +191,8 @@ impl WindowsApi {
pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> Result<()> {
for monitor in monitors.elements_mut() {
if monitor.workspaces().is_empty() {
let mut workspace = Workspace::default();
let monitor_id = monitor.id();
if let Some(workspace) = monitor.workspaces_mut().front_mut() {
// EnumWindows will enumerate through windows on all monitors
Self::enum_windows(
windows_callbacks::enum_window,
@@ -200,7 +209,7 @@ impl WindowsApi {
for container in workspace.containers_mut() {
for window in container.windows() {
if Self::monitor_from_window(window.hwnd()) != monitor.id() {
if Self::monitor_from_window(window.hwnd()) != monitor_id {
windows_on_other_monitors.push(window.hwnd().0);
}
}
@@ -209,8 +218,6 @@ impl WindowsApi {
for hwnd in windows_on_other_monitors {
workspace.remove_window(hwnd)?;
}
monitor.workspaces_mut().push_back(workspace);
}
}

View File

@@ -16,6 +16,17 @@ use crate::window_manager_event::WindowManagerEvent;
use crate::windows_api::WindowsApi;
use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL;
pub extern "system" fn valid_display_monitors(
hmonitor: HMONITOR,
_: HDC,
_: *mut RECT,
lparam: LPARAM,
) -> BOOL {
let monitors = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
monitors.push(hmonitor.0);
true.into()
}
pub extern "system" fn enum_display_monitor(
hmonitor: HMONITOR,
_: HDC,
@@ -23,6 +34,14 @@ pub extern "system" fn enum_display_monitor(
lparam: LPARAM,
) -> BOOL {
let monitors = unsafe { &mut *(lparam.0 as *mut Ring<Monitor>) };
// Don't duplicate a monitor that is already being managed
for monitor in monitors.elements() {
if monitor.id() == hmonitor.0 {
return true.into();
}
}
if let Ok(m) = WindowsApi::monitor(hmonitor) {
monitors.elements_mut().push_back(m);
}

View File

@@ -527,11 +527,10 @@ fn main() -> Result<()> {
None
};
let script = if let Some(exec) = exec {
format!("Start-Process '{}' -WindowStyle hidden", exec)
} else {
String::from("Start-Process komorebi -WindowStyle hidden")
};
let script = exec.map_or_else(
|| String::from("Start-Process komorebi -WindowStyle hidden"),
|exec| format!("Start-Process '{}' -WindowStyle hidden", exec),
);
match powershell_script::run(&script, true) {
Ok(output) => {