mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-20 15:51:32 +02:00
This commit is the result of a long investigation with @berknam on Discord which uncovered that when the channels used by the *_manager modules are full, the window manager can enter a completely locked state which will require a hard restart of komorebi.exe. In order to avoid entering this locked state, *_manager modules now no longer publicly expose event_tx for sending notifications. Instead, a new public fn send_notification is exposed which will use try_send to attempt to send notifications in a non-blocking manner and log warnings if the channel is full and the notification is dropped.
180 lines
6.9 KiB
Rust
180 lines
6.9 KiB
Rust
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
|
|
use crossbeam_channel::Receiver;
|
|
use crossbeam_channel::Sender;
|
|
use crossbeam_utils::atomic::AtomicConsume;
|
|
use parking_lot::Mutex;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::atomic::AtomicU8;
|
|
use std::sync::Arc;
|
|
use std::sync::OnceLock;
|
|
use windows::Win32::Foundation::HWND;
|
|
|
|
use crate::Window;
|
|
use crate::WindowManager;
|
|
use crate::WindowsApi;
|
|
|
|
pub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);
|
|
pub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);
|
|
|
|
static KNOWN_HWNDS: OnceLock<Mutex<Vec<isize>>> = OnceLock::new();
|
|
|
|
pub struct Notification;
|
|
|
|
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
|
|
|
pub fn known_hwnds() -> Vec<isize> {
|
|
let known = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new())).lock();
|
|
known.iter().copied().collect()
|
|
}
|
|
|
|
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
|
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
|
|
}
|
|
|
|
fn event_tx() -> Sender<Notification> {
|
|
channel().0.clone()
|
|
}
|
|
|
|
fn event_rx() -> Receiver<Notification> {
|
|
channel().1.clone()
|
|
}
|
|
|
|
pub fn send_notification() {
|
|
if event_tx().try_send(Notification).is_err() {
|
|
tracing::warn!("channel is full; dropping 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();
|
|
event_tx().send(Notification)?;
|
|
|
|
'receiver: for _ in receiver {
|
|
let known_hwnds = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new()));
|
|
if !TRANSPARENCY_ENABLED.load_consume() {
|
|
for hwnd in known_hwnds.lock().iter() {
|
|
if let Err(error) = Window::from(*hwnd).opaque() {
|
|
tracing::error!("failed to make window {hwnd} opaque: {error}")
|
|
}
|
|
}
|
|
|
|
continue 'receiver;
|
|
}
|
|
|
|
known_hwnds.lock().clear();
|
|
|
|
// Check the wm state every time we receive a notification
|
|
let state = wm.lock();
|
|
|
|
let focused_monitor_idx = state.focused_monitor_idx();
|
|
|
|
'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {
|
|
let focused_workspace_idx = m.focused_workspace_idx();
|
|
|
|
'workspaces: for (workspace_idx, ws) in m.workspaces().iter().enumerate() {
|
|
// Only operate on the focused workspace of each monitor
|
|
// Workspaces with tiling disabled don't have transparent windows
|
|
if !ws.tile() || workspace_idx != focused_workspace_idx {
|
|
for window in ws.visible_windows().iter().flatten() {
|
|
if let Err(error) = window.opaque() {
|
|
let hwnd = window.hwnd;
|
|
tracing::error!("failed to make window {hwnd} opaque: {error}")
|
|
}
|
|
}
|
|
|
|
continue 'workspaces;
|
|
}
|
|
|
|
// Monocle container is never transparent
|
|
if let Some(monocle) = ws.monocle_container() {
|
|
if let Some(window) = monocle.focused_window() {
|
|
if let Err(error) = window.opaque() {
|
|
let hwnd = window.hwnd;
|
|
tracing::error!("failed to make monocle window {hwnd} opaque: {error}")
|
|
}
|
|
}
|
|
|
|
continue 'monitors;
|
|
}
|
|
|
|
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
|
|
let is_maximized = WindowsApi::is_zoomed(HWND(foreground_hwnd));
|
|
|
|
if is_maximized {
|
|
if let Err(error) = Window::from(foreground_hwnd).opaque() {
|
|
let hwnd = foreground_hwnd;
|
|
tracing::error!("failed to make maximized window {hwnd} opaque: {error}")
|
|
}
|
|
|
|
continue 'monitors;
|
|
}
|
|
|
|
for (idx, c) in ws.containers().iter().enumerate() {
|
|
// Update the transparency for all containers on this workspace
|
|
|
|
// If the window is not focused on the current workspace, or isn't on the focused monitor
|
|
// make it transparent
|
|
#[allow(clippy::collapsible_else_if)]
|
|
if idx != ws.focused_container_idx() || monitor_idx != focused_monitor_idx {
|
|
let focused_window_idx = c.focused_window_idx();
|
|
for (window_idx, window) in c.windows().iter().enumerate() {
|
|
if window_idx == focused_window_idx {
|
|
match window.transparent() {
|
|
Err(error) => {
|
|
let hwnd = foreground_hwnd;
|
|
tracing::error!(
|
|
"failed to make unfocused window {hwnd} transparent: {error}"
|
|
)
|
|
}
|
|
Ok(..) => {
|
|
known_hwnds.lock().push(window.hwnd);
|
|
}
|
|
}
|
|
} else {
|
|
// just in case, this is useful when people are clicking around
|
|
// on unfocused stackbar tabs
|
|
known_hwnds.lock().push(window.hwnd);
|
|
}
|
|
}
|
|
// Otherwise, make it opaque
|
|
} else {
|
|
let focused_window_idx = c.focused_window_idx();
|
|
for (window_idx, window) in c.windows().iter().enumerate() {
|
|
if window_idx != focused_window_idx {
|
|
known_hwnds.lock().push(window.hwnd);
|
|
} else {
|
|
if let Err(error) =
|
|
c.focused_window().copied().unwrap_or_default().opaque()
|
|
{
|
|
let hwnd = foreground_hwnd;
|
|
tracing::error!(
|
|
"failed to make focused window {hwnd} opaque: {error}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|