Skip to content

Commit 4c3ccbb

Browse files
committed
WIP
1 parent 30c22f5 commit 4c3ccbb

File tree

3 files changed

+179
-31
lines changed

3 files changed

+179
-31
lines changed

komorebi/src/process_event.rs

+26-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::fs::OpenOptions;
23
use std::sync::atomic::Ordering;
34
use std::sync::Arc;
@@ -717,41 +718,51 @@ impl WindowManager {
717718
}
718719

719720
tracing::trace!("updating list of known hwnds");
720-
let mut known_hwnds = vec![];
721-
for monitor in self.monitors() {
722-
for workspace in monitor.workspaces() {
721+
let mut known_hwnds = HashMap::new();
722+
for (m_idx, monitor) in self.monitors().iter().enumerate() {
723+
for (w_idx, workspace) in monitor.workspaces().iter().enumerate() {
723724
for container in workspace.containers() {
724725
for window in container.windows() {
725-
known_hwnds.push(window.hwnd);
726+
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
726727
}
727728
}
728729

729730
for window in workspace.floating_windows() {
730-
known_hwnds.push(window.hwnd);
731+
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
731732
}
732733

733734
if let Some(window) = workspace.maximized_window() {
734-
known_hwnds.push(window.hwnd);
735+
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
735736
}
736737

737738
if let Some(container) = workspace.monocle_container() {
738739
for window in container.windows() {
739-
known_hwnds.push(window.hwnd);
740+
known_hwnds.insert(window.hwnd, (m_idx, w_idx));
740741
}
741742
}
742743
}
743744
}
744745

745-
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
746-
let file = OpenOptions::new()
747-
.write(true)
748-
.truncate(true)
749-
.create(true)
750-
.open(hwnd_json)?;
746+
if self.known_hwnds != known_hwnds {
747+
// Update reaper cache
748+
{
749+
let mut reaper_cache = crate::reaper::HWNDS_CACHE.lock();
750+
*reaper_cache = known_hwnds.clone();
751+
}
752+
753+
// Save to file
754+
let hwnd_json = DATA_DIR.join("komorebi.hwnd.json");
755+
let file = OpenOptions::new()
756+
.write(true)
757+
.truncate(true)
758+
.create(true)
759+
.open(hwnd_json)?;
751760

752-
serde_json::to_writer_pretty(&file, &known_hwnds)?;
761+
serde_json::to_writer_pretty(&file, &known_hwnds.keys().collect::<Vec<_>>())?;
753762

754-
self.known_hwnds = known_hwnds;
763+
// Store new hwnds
764+
self.known_hwnds = known_hwnds;
765+
}
755766

756767
notify_subscribers(
757768
Notification {

komorebi/src/reaper.rs

+150-14
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,113 @@
11
#![deny(clippy::unwrap_used, clippy::expect_used)]
22

33
use crate::border_manager;
4+
use crate::Window;
45
use crate::WindowManager;
6+
7+
use crossbeam_channel::Receiver;
8+
use crossbeam_channel::Sender;
59
use parking_lot::Mutex;
10+
use std::collections::HashMap;
611
use std::sync::Arc;
12+
use std::sync::OnceLock;
713
use std::time::Duration;
14+
use lazy_static::lazy_static;
815

9-
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
16+
lazy_static! {
17+
pub static ref HWNDS_CACHE: Arc<Mutex<HashMap<isize, (usize, usize)>>> = Arc::new(Mutex::new(HashMap::new()));
18+
}
19+
20+
pub struct OrphanNotification(pub HashMap<isize, (usize, usize)>);
21+
pub struct HwndsNotification(pub Vec<isize>);
22+
23+
static ORPHAN_CHANNEL: OnceLock<(Sender<OrphanNotification>, Receiver<OrphanNotification>)> = OnceLock::new();
24+
static HWNDS_CHANNEL: OnceLock<(Sender<HwndsNotification>, Receiver<HwndsNotification>)> = OnceLock::new();
25+
26+
pub fn orphan_channel() -> &'static (Sender<OrphanNotification>, Receiver<OrphanNotification>) {
27+
ORPHAN_CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
28+
}
29+
30+
fn orphan_event_tx() -> Sender<OrphanNotification> {
31+
orphan_channel().0.clone()
32+
}
33+
34+
fn orphan_event_rx() -> Receiver<OrphanNotification> {
35+
orphan_channel().1.clone()
36+
}
37+
38+
pub fn hwnds_channel() -> &'static (Sender<HwndsNotification>, Receiver<HwndsNotification>) {
39+
HWNDS_CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
40+
}
41+
42+
fn hwnds_event_tx() -> Sender<HwndsNotification> {
43+
hwnds_channel().0.clone()
44+
}
45+
46+
fn hwnds_event_rx() -> Receiver<HwndsNotification> {
47+
hwnds_channel().1.clone()
48+
}
49+
50+
pub fn send_notification(hwnds: HashMap<isize, (usize, usize)>) {
51+
if orphan_event_tx().try_send(OrphanNotification(hwnds)).is_err() {
52+
tracing::warn!("channel is full; dropping notification")
53+
}
54+
}
55+
56+
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
57+
watch_for_orphans(wm.clone());
1058
std::thread::spawn(move || loop {
11-
match find_orphans(wm.clone()) {
59+
match handle_notifications(wm.clone()) {
1260
Ok(()) => {
1361
tracing::warn!("restarting finished thread");
1462
}
1563
Err(error) => {
16-
if cfg!(debug_assertions) {
17-
tracing::error!("restarting failed thread: {:?}", error)
18-
} else {
19-
tracing::error!("restarting failed thread: {}", error)
20-
}
64+
tracing::warn!("restarting failed thread: {}", error);
2165
}
2266
}
2367
});
2468
}
2569

26-
pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
27-
tracing::info!("watching");
70+
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
71+
tracing::info!("listening");
2872

29-
let arc = wm.clone();
73+
let receiver = orphan_event_rx();
3074

31-
loop {
32-
std::thread::sleep(Duration::from_secs(1));
33-
34-
let mut wm = arc.lock();
75+
'receiver: for notification in receiver {
76+
let orphan_hwnds = notification.0;
77+
let mut wm = wm.lock();
3578
let offset = wm.work_area_offset;
3679

3780
let mut update_borders = false;
3881

82+
for (hwnd, (m_idx, w_idx)) in orphan_hwnds.iter() {
83+
if let Some(monitor) = wm.monitors_mut().get_mut(m_idx) {
84+
let work_area = *monitor.work_area_size();
85+
let window_based_work_area_offset = (
86+
monitor.window_based_work_area_offset_limit(),
87+
monitor.window_based_work_area_offset(),
88+
);
89+
90+
let offset = if monitor.work_area_offset().is_some() {
91+
monitor.work_area_offset()
92+
} else {
93+
offset
94+
};
95+
96+
if let Some(workspace) = monitor.workspaces_mut().get_mut() {
97+
workspace.remove_window(hwnd);
98+
workspace.update(&work_area, offset, window_based_work_area_offset)?;
99+
update_borders = true;
100+
tracing::info!(
101+
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
102+
reaped_orphans.0,
103+
reaped_orphans.1,
104+
i,
105+
j
106+
);
107+
}
108+
}
109+
}
110+
39111
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
40112
let work_area = *monitor.work_area_size();
41113
let window_based_work_area_offset = (
@@ -62,11 +134,75 @@ pub fn find_orphans(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
62134
j
63135
);
64136
}
137+
138+
tracing::info!(
139+
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
140+
reaped_orphans.0,
141+
reaped_orphans.1,
142+
i,
143+
j
144+
);
65145
}
66146
}
67147

68148
if update_borders {
69149
border_manager::send_notification(None);
70150
}
71151
}
152+
153+
Ok(())
154+
}
155+
156+
pub fn watch_for_orphans(wm: Arc<Mutex<WindowManager>>) {
157+
// Cache current hwnds
158+
{
159+
let mut cache = HWNDS_CACHE.lock();
160+
*cache = wm.lock().known_hwnds.clone();
161+
}
162+
163+
std::thread::spawn(move || loop {
164+
match find_orphans() {
165+
Ok(()) => {
166+
tracing::warn!("restarting finished thread");
167+
}
168+
Err(error) => {
169+
if cfg!(debug_assertions) {
170+
tracing::error!("restarting failed thread: {:?}", error)
171+
} else {
172+
tracing::error!("restarting failed thread: {}", error)
173+
}
174+
}
175+
}
176+
});
177+
}
178+
179+
pub fn find_orphans() -> color_eyre::Result<()> {
180+
tracing::info!("watching");
181+
182+
loop {
183+
std::thread::sleep(Duration::from_millis(20));
184+
185+
let cache = HWNDS_CACHE.lock();
186+
let mut orphan_hwnds = HashMap::new();
187+
188+
for (hwnd, (m_idx, w_idx)) in cache.iter() {
189+
let window = Window::from(*hwnd);
190+
191+
if !window.is_window()
192+
// This one is a hack because WINWORD.EXE is an absolute trainwreck of an app
193+
// when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED
194+
// (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the
195+
// docs is closed
196+
//
197+
// I hate every single person who worked on Microsoft Office 365, especially Word
198+
|| !window.is_visible()
199+
{
200+
orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));
201+
}
202+
}
203+
204+
if !orphan_hwnds.is_empty() {
205+
orphan_event_tx().send(OrphanNotification(orphan_hwnds))?;
206+
}
207+
}
72208
}

komorebi/src/window_manager.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ pub struct WindowManager {
117117
pub pending_move_op: Arc<Option<(usize, usize, isize)>>,
118118
pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,
119119
pub uncloack_to_ignore: usize,
120-
pub known_hwnds: Vec<isize>,
120+
/// Maps each known window hwnd to the (monitor, workspace) index pair managing it
121+
pub known_hwnds: HashMap<isize, (usize, usize)>,
121122
}
122123

123124
#[allow(clippy::struct_excessive_bools)]
@@ -365,7 +366,7 @@ impl WindowManager {
365366
pending_move_op: Arc::new(None),
366367
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
367368
uncloack_to_ignore: 0,
368-
known_hwnds: Vec::new(),
369+
known_hwnds: HashMap::new(),
369370
})
370371
}
371372

0 commit comments

Comments
 (0)