fix(wm): properly update border colors

This commit changes the border manager notification to an enum that can
be a `Notification::Update(Option<isize>)` 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<RenderTarget>`
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.
This commit is contained in:
alex-ds13
2025-04-02 19:45:06 +01:00
committed by Jeezy
parent 0e8ed8aa40
commit 10ab43a8ae
6 changed files with 187 additions and 103 deletions

View File

@@ -9,13 +9,11 @@ use crate::core::Rect;
use crate::windows_api; use crate::windows_api;
use crate::WindowsApi; use crate::WindowsApi;
use crate::WINDOWS_11; use crate::WINDOWS_11;
use color_eyre::eyre::anyhow;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::LazyLock; use std::sync::LazyLock;
use std::sync::OnceLock;
use windows::Win32::Foundation::FALSE; use windows::Win32::Foundation::FALSE;
use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::HWND;
use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LPARAM;
@@ -118,7 +116,7 @@ pub struct Border {
pub hwnd: isize, pub hwnd: isize,
pub id: String, pub id: String,
pub monitor_idx: Option<usize>, pub monitor_idx: Option<usize>,
pub render_target: OnceLock<RenderTarget>, pub render_target: Option<RenderTarget>,
pub tracking_hwnd: isize, pub tracking_hwnd: isize,
pub window_rect: Rect, pub window_rect: Rect,
pub window_kind: WindowKind, pub window_kind: WindowKind,
@@ -136,7 +134,7 @@ impl From<isize> for Border {
hwnd: value, hwnd: value,
id: String::new(), id: String::new(),
monitor_idx: None, monitor_idx: None,
render_target: OnceLock::new(), render_target: None,
tracking_hwnd: 0, tracking_hwnd: 0,
window_rect: Rect::default(), window_rect: Rect::default(),
window_kind: WindowKind::Unfocused, window_kind: WindowKind::Unfocused,
@@ -184,7 +182,7 @@ impl Border {
hwnd: 0, hwnd: 0,
id: container_id, id: container_id,
monitor_idx: Some(monitor_idx), monitor_idx: Some(monitor_idx),
render_target: OnceLock::new(), render_target: None,
tracking_hwnd, tracking_hwnd,
window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(), window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),
window_kind: WindowKind::Unfocused, window_kind: WindowKind::Unfocused,
@@ -243,8 +241,14 @@ impl Border {
let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh); 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 { 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(), pixelSize: Default::default(),
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY, presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
}; };
@@ -265,7 +269,7 @@ impl Border {
.CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties) .CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties)
} { } {
Ok(render_target) => unsafe { Ok(render_target) => unsafe {
border.brush_properties = *BRUSH_PROPERTIES.deref(); self.brush_properties = *BRUSH_PROPERTIES.deref();
for window_kind in [ for window_kind in [
WindowKind::Single, WindowKind::Single,
WindowKind::Stack, WindowKind::Stack,
@@ -283,24 +287,18 @@ impl Border {
}; };
if let Ok(brush) = 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); render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
if border self.render_target = Some(RenderTarget(render_target));
.render_target
.set(RenderTarget(render_target.clone()))
.is_err()
{
return Err(anyhow!("could not store border render target"));
}
border.rounded_rect = { self.rounded_rect = {
let radius = 8.0 + border.width as f32 / 2.0; let radius = 8.0 + self.width as f32 / 2.0;
D2D1_ROUNDED_RECT { D2D1_ROUNDED_RECT {
rect: Default::default(), rect: Default::default(),
radiusX: radius, radiusX: radius,
@@ -308,7 +306,7 @@ impl Border {
} }
}; };
Ok(border) Ok(())
}, },
Err(error) => Err(error.into()), Err(error) => Err(error.into()),
} }
@@ -395,7 +393,7 @@ impl Border {
} }
if !rect.is_same_size_as(&old_rect) { 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_width = (*border_pointer).width;
let border_offset = (*border_pointer).offset; let border_offset = (*border_pointer).offset;
@@ -477,7 +475,7 @@ impl Border {
tracing::error!("failed to update border position {error}"); 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).width = BORDER_WIDTH.load(Ordering::Relaxed);
(*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed); (*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed);

View File

@@ -73,7 +73,10 @@ impl Deref for RenderTarget {
} }
} }
pub struct Notification(pub Option<isize>); pub enum Notification {
Update(Option<isize>),
ForceUpdate,
}
#[derive(Debug, Default, Clone, Copy, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct BorderInfo { pub struct BorderInfo {
@@ -111,7 +114,13 @@ pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
} }
pub fn send_notification(hwnd: Option<isize>) { pub fn send_notification(hwnd: Option<isize>) {
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") tracing::warn!("channel is full; dropping notification")
} }
} }
@@ -175,7 +184,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
tracing::info!("listening"); tracing::info!("listening");
let receiver = event_rx(); let receiver = event_rx();
event_tx().send(Notification(None))?; event_tx().send(Notification::Update(None))?;
let mut previous_snapshot = Ring::default(); let mut previous_snapshot = Ring::default();
let mut previous_pending_move_op = None; let mut previous_pending_move_op = None;
@@ -261,64 +270,75 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
} }
} }
BorderImplementation::Komorebi => { 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 if monitors == previous_snapshot
// handle the window dragging edge case // handle the window dragging edge case
&& pending_move_op == previous_pending_move_op && pending_move_op == previous_pending_move_op
{ {
should_process_notification = false; should_process_notification = false;
} }
// handle the pause edge case // handle the pause edge case
if is_paused && !previous_is_paused { 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 == &notification.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() {
should_process_notification = true; 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 == &notification_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 { if !should_process_notification {
tracing::trace!("monitor state matches latest snapshot, skipping notification"); tracing::trace!("monitor state matches latest snapshot, skipping notification");
@@ -415,6 +435,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
if new_border { if new_border {
border.set_position(&rect, focused_window_hwnd)?; 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(); border.invalidate();
@@ -544,12 +569,20 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect; border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer; let layer_changed = previous_layer != workspace_layer;
let forced_update = matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border let should_invalidate = new_border
|| (last_focus_state != new_focus_state) || (last_focus_state != new_focus_state)
|| layer_changed; || layer_changed
|| forced_update;
if should_invalidate { 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.set_position(&rect, focused_window_hwnd)?;
border.invalidate(); border.invalidate();
} }
@@ -594,12 +627,21 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
border.window_rect = rect; border.window_rect = rect;
let layer_changed = previous_layer != workspace_layer; let layer_changed = previous_layer != workspace_layer;
let forced_update =
matches!(notification, Notification::ForceUpdate);
let should_invalidate = new_border let should_invalidate = new_border
|| (last_focus_state != new_focus_state) || (last_focus_state != new_focus_state)
|| layer_changed; || layer_changed
|| forced_update;
if should_invalidate { 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.set_position(&rect, window.hwnd)?;
border.invalidate(); border.invalidate();
} }

View File

@@ -224,6 +224,7 @@ impl WindowManager {
_ => {} _ => {}
}; };
let mut force_update_borders = false;
match message { match message {
SocketMessage::Promote => self.promote_container_to_front()?, SocketMessage::Promote => self.promote_container_to_front()?,
SocketMessage::PromoteFocus => self.promote_focus_to_front()?, SocketMessage::PromoteFocus => self.promote_focus_to_front()?,
@@ -861,10 +862,12 @@ impl WindowManager {
} }
SocketMessage::Retile => { SocketMessage::Retile => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(false)? self.retile_all(false)?
} }
SocketMessage::RetileWithResizeDimensions => { SocketMessage::RetileWithResizeDimensions => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
force_update_borders = true;
self.retile_all(true)? self.retile_all(true)?
} }
SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,
@@ -1600,6 +1603,7 @@ impl WindowManager {
} }
SocketMessage::ReloadConfiguration => { SocketMessage::ReloadConfiguration => {
Self::reload_configuration(); Self::reload_configuration();
force_update_borders = true;
} }
SocketMessage::ReplaceConfiguration(ref config) => { SocketMessage::ReplaceConfiguration(ref config) => {
// Check that this is a valid static config file first // Check that this is a valid static config file first
@@ -1628,15 +1632,19 @@ impl WindowManager {
// Set self to the new wm instance // Set self to the new wm instance
*self = wm; *self = wm;
force_update_borders = true;
} }
} }
SocketMessage::ReloadStaticConfiguration(ref pathbuf) => { SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {
self.reload_static_configuration(pathbuf)?; self.reload_static_configuration(pathbuf)?;
force_update_borders = true;
} }
SocketMessage::CompleteConfiguration => { SocketMessage::CompleteConfiguration => {
if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) { if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {
INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst); INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);
self.update_focused_workspace(false, false)?; self.update_focused_workspace(false, false)?;
force_update_borders = true;
} }
} }
SocketMessage::WatchConfiguration(enable) => { SocketMessage::WatchConfiguration(enable) => {
@@ -1894,6 +1902,8 @@ impl WindowManager {
self.remove_all_accents()?; self.remove_all_accents()?;
} }
} }
} else if matches!(IMPLEMENTATION.load(), BorderImplementation::Komorebi) {
force_update_borders = true;
} }
} }
SocketMessage::BorderImplementation(implementation) => { SocketMessage::BorderImplementation(implementation) => {
@@ -1906,44 +1916,49 @@ impl WindowManager {
match IMPLEMENTATION.load() { match IMPLEMENTATION.load() {
BorderImplementation::Komorebi => { BorderImplementation::Komorebi => {
self.remove_all_accents()?; self.remove_all_accents()?;
force_update_borders = true;
} }
BorderImplementation::Windows => { BorderImplementation::Windows => {
border_manager::destroy_all_borders()?; border_manager::destroy_all_borders()?;
} }
} }
border_manager::send_notification(None);
} }
} }
SocketMessage::BorderColour(kind, r, g, b) => match kind { SocketMessage::BorderColour(kind, r, g, b) => {
WindowKind::Single => { match kind {
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); 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 => { force_update_borders = true;
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);
}
},
SocketMessage::BorderStyle(style) => { SocketMessage::BorderStyle(style) => {
STYLE.store(style); STYLE.store(style);
force_update_borders = true;
} }
SocketMessage::BorderWidth(width) => { SocketMessage::BorderWidth(width) => {
border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst); border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);
force_update_borders = true;
} }
SocketMessage::BorderOffset(offset) => { SocketMessage::BorderOffset(offset) => {
border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst); border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);
force_update_borders = true;
} }
SocketMessage::Animation(enable, prefix) => match prefix { SocketMessage::Animation(enable, prefix) => match prefix {
Some(prefix) => { Some(prefix) => {
@@ -2124,7 +2139,11 @@ impl WindowManager {
initial_state.has_been_modified(self.as_ref()), 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(); transparency_manager::send_notification();
stackbar_manager::send_notification(); stackbar_manager::send_notification();

View File

@@ -1678,6 +1678,8 @@ impl StaticConfig {
for i in 0..monitor_count { for i in 0..monitor_count {
wm.update_focused_workspace_by_monitor_idx(i)?; 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(()) Ok(())

View File

@@ -295,7 +295,7 @@ pub fn handle_notifications() -> color_eyre::Result<()> {
CURRENT_THEME.store(Some(notification.0)); CURRENT_THEME.store(Some(notification.0));
border_manager::send_notification(None); border_manager::send_force_update();
stackbar_manager::send_notification(); stackbar_manager::send_notification();
} }

View File

@@ -1715,6 +1715,29 @@ impl WindowManager {
Ok(()) 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<()> { pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {
let offset = self.work_area_offset; let offset = self.work_area_offset;