fix(wm): restart bm and wsr notif handlers on fail

This commit ensures that if and when either the border_manager or
workspace_reconciliator notification handlers crash in their respective
threads, they will be restarted instead of killing the whole thread.

This is acheived via a loop running in the spawned threads of both
listeners, which will report whichever error killed the notification
handler functions.

Clippy annotations to deny expect and unwrap calls have been added to
these two modules.

Calls to DestroyWindow for expired borders were sporadically failing in
unpredictable circumstances, so these have been switched out with calls
to CloseWindow which has been working well so far with the stackbar
feature.
This commit is contained in:
LGUG2Z
2024-05-12 17:25:14 -07:00
parent 9fd0c7bf39
commit 39da10de94
5 changed files with 233 additions and 192 deletions

View File

@@ -60,6 +60,7 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
true.into()
}
#[derive(Debug)]
pub struct Border {
pub hwnd: isize,
}
@@ -116,7 +117,7 @@ impl Border {
}
pub fn destroy(&self) -> color_eyre::Result<()> {
WindowsApi::destroy_window(self.hwnd())
WindowsApi::close_window(self.hwnd())
}
pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> {

View File

@@ -1,3 +1,5 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod border;
use crossbeam_channel::Receiver;
@@ -9,6 +11,7 @@ use parking_lot::Mutex;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicI32;
@@ -69,6 +72,11 @@ pub fn event_rx() -> Receiver<Notification> {
pub fn destroy_all_borders() -> color_eyre::Result<()> {
let mut borders = BORDER_STATE.lock();
tracing::info!(
"purging known borders: {:?}",
borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()
);
for (_, border) in borders.iter() {
border.destroy()?;
}
@@ -85,18 +93,35 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
&mut remaining_hwnds as *mut Vec<isize> as isize,
)?;
if !remaining_hwnds.is_empty() {
tracing::info!("purging unknown borders: {:?}", remaining_hwnds);
for hwnd in remaining_hwnds {
Border::from(hwnd).destroy()?;
}
}
Ok(())
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
}
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
let receiver = event_rx();
std::thread::spawn(move || -> color_eyre::Result<()> {
'receiver: for _ in receiver {
let mut borders = BORDER_STATE.lock();
let mut borders_monitors = BORDERS_MONITORS.lock();
@@ -125,8 +150,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
if !ws.tile() {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
{
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
border.destroy()?;
to_remove.push(id.clone());
}
@@ -143,8 +167,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
if let Some(monocle) = ws.monocle_container() {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
{
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
border.destroy()?;
to_remove.push(id.clone());
}
@@ -154,9 +177,16 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
borders.remove(id);
}
let border = borders.entry(monocle.id().clone()).or_insert_with(|| {
Border::create(monocle.id()).expect("border creation failed")
});
let border = match borders.entry(monocle.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(monocle.id()) {
entry.insert(border)
} else {
continue 'receiver;
}
}
};
borders_monitors.insert(monocle.id().clone(), monitor_idx);
@@ -166,10 +196,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
}
let rect = WindowsApi::window_rect(
monocle
.focused_window()
.expect("monocle container has no focused window")
.hwnd(),
monocle.focused_window().copied().unwrap_or_default().hwnd(),
)?;
border.update(&rect)?;
@@ -183,8 +210,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
if is_maximized {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
{
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
border.destroy()?;
to_remove.push(id.clone());
}
@@ -206,7 +232,9 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
let mut to_remove = vec![];
for (id, border) in borders.iter() {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx && !container_ids.contains(id) {
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
&& !container_ids.contains(id)
{
border.destroy()?;
to_remove.push(id.clone());
}
@@ -223,20 +251,23 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
*Z_ORDER.lock() = ZOrder::TopMost;
let mut rect = WindowsApi::window_rect(
c.focused_window()
.expect("container has no focused window")
.hwnd(),
c.focused_window().copied().unwrap_or_default().hwnd(),
)?;
while WindowsApi::lbutton_is_pressed() {
let border = borders.entry(c.id().clone()).or_insert_with(|| {
Border::create(c.id()).expect("border creation failed")
});
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'receiver;
}
}
};
let new_rect = WindowsApi::window_rect(
c.focused_window()
.expect("container has no focused window")
.hwnd(),
c.focused_window().copied().unwrap_or_default().hwnd(),
)?;
if rect != new_rect {
@@ -251,9 +282,16 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
}
// Get the border entry for this container from the map or create one
let border = borders.entry(c.id().clone()).or_insert_with(|| {
Border::create(c.id()).expect("border creation failed")
});
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) = Border::create(c.id()) {
entry.insert(border)
} else {
continue 'receiver;
}
}
};
borders_monitors.insert(c.id().clone(), monitor_idx);
@@ -275,9 +313,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
}
let rect = WindowsApi::window_rect(
c.focused_window()
.expect("container has no focused window")
.hwnd(),
c.focused_window().copied().unwrap_or_default().hwnd(),
)?;
border.update(&rect)?;
@@ -287,7 +323,6 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
}
Ok(())
});
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)]

View File

@@ -38,7 +38,7 @@ use crate::PERMAIGNORE_CLASSES;
use crate::REGEX_IDENTIFIERS;
use crate::WSL2_UI_PROCESSES;
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
#[derive(Debug, Default, Clone, Copy, Deserialize, JsonSchema)]
pub struct Window {
pub hwnd: isize,
}

View File

@@ -121,7 +121,6 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;
use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;
use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;
use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;
use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;
use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;
use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;
@@ -433,13 +432,6 @@ impl WindowsApi {
}
}
pub fn destroy_window(hwnd: HWND) -> Result<()> {
match Self::post_message(hwnd, WM_DESTROY, WPARAM(0), LPARAM(0)) {
Ok(()) => Ok(()),
Err(_) => Err(anyhow!("could not close window")),
}
}
pub fn hide_window(hwnd: HWND) {
Self::show_window(hwnd, SW_HIDE);
}

View File

@@ -1,3 +1,5 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use crate::WindowManager;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
@@ -26,10 +28,22 @@ pub fn event_rx() -> Receiver<Notification> {
}
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
std::thread::spawn(move || loop {
match handle_notifications(wm.clone()) {
Ok(()) => {
tracing::warn!("restarting finished thread");
}
Err(error) => {
tracing::warn!("restarting failed thread: {}", error);
}
}
});
}
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
tracing::info!("listening");
let receiver = event_rx();
std::thread::spawn(move || -> color_eyre::Result<()> {
for notification in receiver {
tracing::info!("running reconciliation");
let mut wm = wm.lock();
@@ -52,5 +66,4 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
}
Ok(())
});
}