mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-24 18:31:22 +01:00
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:
@@ -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<usize>,
|
||||
pub render_target: OnceLock<RenderTarget>,
|
||||
pub render_target: Option<RenderTarget>,
|
||||
pub tracking_hwnd: isize,
|
||||
pub window_rect: Rect,
|
||||
pub window_kind: WindowKind,
|
||||
@@ -136,7 +134,7 @@ impl From<isize> 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);
|
||||
|
||||
|
||||
@@ -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)]
|
||||
pub struct BorderInfo {
|
||||
@@ -111,7 +114,13 @@ pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -175,7 +184,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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<Mutex<WindowManager>>) -> 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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user