From 10ab43a8ae4d645be94dab5f9b868f5cc6373629 Mon Sep 17 00:00:00 2001 From: alex-ds13 <145657253+alex-ds13@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:45:06 +0100 Subject: [PATCH] fix(wm): properly update border colors This commit changes the border manager notification to an enum that can be a `Notification::Update(Option)` which should work like before or a `Notification::ForceUpdate` which will always update the borders and it will also update the border's brushes. To do so, this commit moved the brush creation from the `create()` function to a new `update_brushes` function on the `Border` itself and it changed the `Border` `render_target` from a `OnceLock` to an `Option` so that we can always change it when we need to. This commit also adds a new function called `send_force_update()` to the border manager to make it easier to send this notification. This commit also added a check for `force_update_borders` on the `process_command` function so that any command that reloads the configuration, changes the theme or changes any border config can set it to `true`. When this is true we send the `ForceUpdate` notification, if false we send the normal `Update` notification as before. The theme manager now always sends the `ForceUpdate` notification to the border manager. This commit adds a new function to the window manager called `apply_wallpaper_for_monitor_workspace` which in turn calls the `apply_wallpaper` function for the workspace specified with the `monitor_idx` and `workspace_idx` given (if the workspace in question doesn't have a wallpaper defined this won't do anything). All these changes make it so the wallpaper theme generation colors are properly applied in all situations. --- komorebi/src/border_manager/border.rs | 40 ++++--- komorebi/src/border_manager/mod.rs | 158 ++++++++++++++++---------- komorebi/src/process_command.rs | 65 +++++++---- komorebi/src/static_config.rs | 2 + komorebi/src/theme_manager.rs | 2 +- komorebi/src/window_manager.rs | 23 ++++ 6 files changed, 187 insertions(+), 103 deletions(-) diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index 2fd26c94..ef1d4bce 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -9,13 +9,11 @@ use crate::core::Rect; use crate::windows_api; use crate::WindowsApi; use crate::WINDOWS_11; -use color_eyre::eyre::anyhow; use std::collections::HashMap; use std::ops::Deref; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::sync::LazyLock; -use std::sync::OnceLock; use windows::Win32::Foundation::FALSE; use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::LPARAM; @@ -118,7 +116,7 @@ pub struct Border { pub hwnd: isize, pub id: String, pub monitor_idx: Option, - pub render_target: OnceLock, + pub render_target: Option, pub tracking_hwnd: isize, pub window_rect: Rect, pub window_kind: WindowKind, @@ -136,7 +134,7 @@ impl From for Border { hwnd: value, id: String::new(), monitor_idx: None, - render_target: OnceLock::new(), + render_target: None, tracking_hwnd: 0, window_rect: Rect::default(), window_kind: WindowKind::Unfocused, @@ -184,7 +182,7 @@ impl Border { hwnd: 0, id: container_id, monitor_idx: Some(monitor_idx), - render_target: OnceLock::new(), + render_target: None, tracking_hwnd, window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(), window_kind: WindowKind::Unfocused, @@ -243,8 +241,14 @@ impl Border { let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh); } + border.update_brushes()?; + + Ok(border) + } + + pub fn update_brushes(&mut self) -> color_eyre::Result<()> { let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES { - hwnd: HWND(windows_api::as_ptr!(border.hwnd)), + hwnd: HWND(windows_api::as_ptr!(self.hwnd)), pixelSize: Default::default(), presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY, }; @@ -265,7 +269,7 @@ impl Border { .CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties) } { Ok(render_target) => unsafe { - border.brush_properties = *BRUSH_PROPERTIES.deref(); + self.brush_properties = *BRUSH_PROPERTIES.deref(); for window_kind in [ WindowKind::Single, WindowKind::Stack, @@ -283,24 +287,18 @@ impl Border { }; if let Ok(brush) = - render_target.CreateSolidColorBrush(&color, Some(&border.brush_properties)) + render_target.CreateSolidColorBrush(&color, Some(&self.brush_properties)) { - border.brushes.insert(window_kind, brush); + self.brushes.insert(window_kind, brush); } } render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); - if border - .render_target - .set(RenderTarget(render_target.clone())) - .is_err() - { - return Err(anyhow!("could not store border render target")); - } + self.render_target = Some(RenderTarget(render_target)); - border.rounded_rect = { - let radius = 8.0 + border.width as f32 / 2.0; + self.rounded_rect = { + let radius = 8.0 + self.width as f32 / 2.0; D2D1_ROUNDED_RECT { rect: Default::default(), radiusX: radius, @@ -308,7 +306,7 @@ impl Border { } }; - Ok(border) + Ok(()) }, Err(error) => Err(error.into()), } @@ -395,7 +393,7 @@ impl Border { } if !rect.is_same_size_as(&old_rect) { - if let Some(render_target) = (*border_pointer).render_target.get() { + if let Some(render_target) = (*border_pointer).render_target.as_ref() { let border_width = (*border_pointer).width; let border_offset = (*border_pointer).offset; @@ -477,7 +475,7 @@ impl Border { tracing::error!("failed to update border position {error}"); } - if let Some(render_target) = (*border_pointer).render_target.get() { + if let Some(render_target) = (*border_pointer).render_target.as_ref() { (*border_pointer).width = BORDER_WIDTH.load(Ordering::Relaxed); (*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed); diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 9c15e785..a35872af 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -73,7 +73,10 @@ impl Deref for RenderTarget { } } -pub struct Notification(pub Option); +pub enum Notification { + Update(Option), + ForceUpdate, +} #[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct BorderInfo { @@ -111,7 +114,13 @@ pub fn window_border(hwnd: isize) -> Option { } pub fn send_notification(hwnd: Option) { - if event_tx().try_send(Notification(hwnd)).is_err() { + if event_tx().try_send(Notification::Update(hwnd)).is_err() { + tracing::warn!("channel is full; dropping notification") + } +} + +pub fn send_force_update() { + if event_tx().try_send(Notification::ForceUpdate).is_err() { tracing::warn!("channel is full; dropping notification") } } @@ -175,7 +184,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result tracing::info!("listening"); let receiver = event_rx(); - event_tx().send(Notification(None))?; + event_tx().send(Notification::Update(None))?; let mut previous_snapshot = Ring::default(); let mut previous_pending_move_op = None; @@ -261,64 +270,75 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } } BorderImplementation::Komorebi => { - let mut should_process_notification = true; + let should_process_notification = match notification { + Notification::Update(notification_hwnd) => { + let mut should_process_notification = true; - if monitors == previous_snapshot - // handle the window dragging edge case - && pending_move_op == previous_pending_move_op - { - should_process_notification = false; - } + if monitors == previous_snapshot + // handle the window dragging edge case + && pending_move_op == previous_pending_move_op + { + should_process_notification = false; + } - // handle the pause edge case - if is_paused && !previous_is_paused { - should_process_notification = true; - } - - // handle the unpause edge case - if previous_is_paused && !is_paused { - should_process_notification = true; - } - - // handle the retile edge case - if !should_process_notification && BORDER_STATE.lock().is_empty() { - should_process_notification = true; - } - - // when we switch focus to/from a floating window - let switch_focus_to_from_floating_window = floating_window_hwnds.iter().any(|fw| { - // if we switch focus to a floating window - fw == ¬ification.0.unwrap_or_default() || - // if there is any floating window with a `WindowKind::Floating` border - // that no longer is the foreground window then we need to update that - // border. - (fw != &foreground_window - && window_border(*fw) - .is_some_and(|b| b.window_kind == WindowKind::Floating)) - }); - - // when the focused window has an `Unfocused` border kind, usually this happens if - // we focus an admin window and then refocus the previously focused window. For - // komorebi it will have the same state has before, however the previously focused - // window changed its border to unfocused so now we need to update it again. - if !should_process_notification - && window_border(notification.0.unwrap_or_default()) - .is_some_and(|b| b.window_kind == WindowKind::Unfocused) - { - should_process_notification = true; - } - - if !should_process_notification && switch_focus_to_from_floating_window { - should_process_notification = true; - } - - if !should_process_notification { - if let Some(ref previous) = previous_notification { - if previous.0.unwrap_or_default() != notification.0.unwrap_or_default() { + // handle the pause edge case + if is_paused && !previous_is_paused { should_process_notification = true; } + + // handle the unpause edge case + if previous_is_paused && !is_paused { + should_process_notification = true; + } + + // handle the retile edge case + if !should_process_notification && BORDER_STATE.lock().is_empty() { + should_process_notification = true; + } + + // when we switch focus to/from a floating window + let switch_focus_to_from_floating_window = + floating_window_hwnds.iter().any(|fw| { + // if we switch focus to a floating window + fw == ¬ification_hwnd.unwrap_or_default() || + // if there is any floating window with a `WindowKind::Floating` border + // that no longer is the foreground window then we need to update that + // border. + (fw != &foreground_window + && window_border(*fw) + .is_some_and(|b| b.window_kind == WindowKind::Floating)) + }); + + // when the focused window has an `Unfocused` border kind, usually this happens if + // we focus an admin window and then refocus the previously focused window. For + // komorebi it will have the same state has before, however the previously focused + // window changed its border to unfocused so now we need to update it again. + if !should_process_notification + && window_border(notification_hwnd.unwrap_or_default()) + .is_some_and(|b| b.window_kind == WindowKind::Unfocused) + { + should_process_notification = true; + } + + if !should_process_notification && switch_focus_to_from_floating_window { + should_process_notification = true; + } + + if !should_process_notification { + if let Some(Notification::Update(ref previous)) = previous_notification + { + if previous.unwrap_or_default() + != notification_hwnd.unwrap_or_default() + { + should_process_notification = true; + } + } + } + + should_process_notification } - } + Notification::ForceUpdate => true, + }; if !should_process_notification { tracing::trace!("monitor state matches latest snapshot, skipping notification"); @@ -415,6 +435,11 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result if new_border { border.set_position(&rect, focused_window_hwnd)?; + } else if matches!(notification, Notification::ForceUpdate) { + // Update the border brushes if there was a forced update + // notification and this is not a new border (new border's + // already have their brushes updated on creation) + border.update_brushes()?; } border.invalidate(); @@ -544,12 +569,20 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result border.window_rect = rect; let layer_changed = previous_layer != workspace_layer; + let forced_update = matches!(notification, Notification::ForceUpdate); let should_invalidate = new_border || (last_focus_state != new_focus_state) - || layer_changed; + || layer_changed + || forced_update; if should_invalidate { + if forced_update && !new_border { + // Update the border brushes if there was a forced update + // notification and this is not a new border (new border's + // already have their brushes updated on creation) + border.update_brushes()?; + } border.set_position(&rect, focused_window_hwnd)?; border.invalidate(); } @@ -594,12 +627,21 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result border.window_rect = rect; let layer_changed = previous_layer != workspace_layer; + let forced_update = + matches!(notification, Notification::ForceUpdate); let should_invalidate = new_border || (last_focus_state != new_focus_state) - || layer_changed; + || layer_changed + || forced_update; if should_invalidate { + if forced_update && !new_border { + // Update the border brushes if there was a forced update + // notification and this is not a new border (new border's + // already have their brushes updated on creation) + border.update_brushes()?; + } border.set_position(&rect, window.hwnd)?; border.invalidate(); } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 85d2931b..65135983 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -224,6 +224,7 @@ impl WindowManager { _ => {} }; + let mut force_update_borders = false; match message { SocketMessage::Promote => self.promote_container_to_front()?, SocketMessage::PromoteFocus => self.promote_focus_to_front()?, @@ -861,10 +862,12 @@ impl WindowManager { } SocketMessage::Retile => { border_manager::destroy_all_borders()?; + force_update_borders = true; self.retile_all(false)? } SocketMessage::RetileWithResizeDimensions => { border_manager::destroy_all_borders()?; + force_update_borders = true; self.retile_all(true)? } SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, @@ -1600,6 +1603,7 @@ impl WindowManager { } SocketMessage::ReloadConfiguration => { Self::reload_configuration(); + force_update_borders = true; } SocketMessage::ReplaceConfiguration(ref config) => { // Check that this is a valid static config file first @@ -1628,15 +1632,19 @@ impl WindowManager { // Set self to the new wm instance *self = wm; + + force_update_borders = true; } } SocketMessage::ReloadStaticConfiguration(ref pathbuf) => { self.reload_static_configuration(pathbuf)?; + force_update_borders = true; } SocketMessage::CompleteConfiguration => { if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) { INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst); self.update_focused_workspace(false, false)?; + force_update_borders = true; } } SocketMessage::WatchConfiguration(enable) => { @@ -1894,6 +1902,8 @@ impl WindowManager { self.remove_all_accents()?; } } + } else if matches!(IMPLEMENTATION.load(), BorderImplementation::Komorebi) { + force_update_borders = true; } } SocketMessage::BorderImplementation(implementation) => { @@ -1906,44 +1916,49 @@ impl WindowManager { match IMPLEMENTATION.load() { BorderImplementation::Komorebi => { self.remove_all_accents()?; + force_update_borders = true; } BorderImplementation::Windows => { border_manager::destroy_all_borders()?; } } - - border_manager::send_notification(None); } } - SocketMessage::BorderColour(kind, r, g, b) => match kind { - WindowKind::Single => { - border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + SocketMessage::BorderColour(kind, r, g, b) => { + match kind { + WindowKind::Single => { + border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Stack => { + border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Monocle => { + border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Unfocused => { + border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::UnfocusedLocked => { + border_manager::UNFOCUSED_LOCKED + .store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Floating => { + border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } } - WindowKind::Stack => { - border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::Monocle => { - border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::Unfocused => { - border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::UnfocusedLocked => { - border_manager::UNFOCUSED_LOCKED - .store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::Floating => { - border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - }, + force_update_borders = true; + } SocketMessage::BorderStyle(style) => { STYLE.store(style); + force_update_borders = true; } SocketMessage::BorderWidth(width) => { border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst); + force_update_borders = true; } SocketMessage::BorderOffset(offset) => { border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst); + force_update_borders = true; } SocketMessage::Animation(enable, prefix) => match prefix { Some(prefix) => { @@ -2124,7 +2139,11 @@ impl WindowManager { initial_state.has_been_modified(self.as_ref()), )?; - border_manager::send_notification(None); + if force_update_borders { + border_manager::send_force_update(); + } else { + border_manager::send_notification(None); + } transparency_manager::send_notification(); stackbar_manager::send_notification(); diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index c6dee3e6..c6e14c42 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -1678,6 +1678,8 @@ impl StaticConfig { for i in 0..monitor_count { wm.update_focused_workspace_by_monitor_idx(i)?; + let ws_idx = wm.focused_workspace_idx_for_monitor_idx(i)?; + wm.apply_wallpaper_for_monitor_workspace(i, ws_idx)?; } Ok(()) diff --git a/komorebi/src/theme_manager.rs b/komorebi/src/theme_manager.rs index 11a86225..b4be85ec 100644 --- a/komorebi/src/theme_manager.rs +++ b/komorebi/src/theme_manager.rs @@ -295,7 +295,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> { CURRENT_THEME.store(Some(notification.0)); - border_manager::send_notification(None); + border_manager::send_force_update(); stackbar_manager::send_notification(); } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index d6eae048..7f3dd5aa 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -1715,6 +1715,29 @@ impl WindowManager { Ok(()) } + /// Check for an existing wallpaper definition on the workspace/monitor index pair and apply it + /// if it exists + #[tracing::instrument(skip(self))] + pub fn apply_wallpaper_for_monitor_workspace( + &mut self, + monitor_idx: usize, + workspace_idx: usize, + ) -> Result<()> { + let monitor = self + .monitors_mut() + .get_mut(monitor_idx) + .ok_or_else(|| anyhow!("there is no monitor"))?; + + let hmonitor = monitor.id(); + + let workspace = monitor + .workspaces() + .get(workspace_idx) + .ok_or_else(|| anyhow!("there is no workspace"))?; + + workspace.apply_wallpaper(hmonitor) + } + pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> { let offset = self.work_area_offset;