feat(wm): allow setting wallpaper per monitor

This commit adds the option to set `Wallpaper` per monitor. When
changing workspaces it will first check for a workspace wallpaper, if
there is none it then checks for a monitor wallpaper.
This commit is contained in:
alex-ds13
2025-04-03 19:22:03 +01:00
committed by LGUG2Z
parent 10ab43a8ae
commit 2a5a960c34
7 changed files with 299 additions and 10 deletions

View File

@@ -21,6 +21,7 @@ use crate::workspace::WorkspaceLayer;
use crate::DefaultLayout;
use crate::Layout;
use crate::OperationDirection;
use crate::Wallpaper;
use crate::WindowsApi;
use crate::DEFAULT_CONTAINER_PADDING;
use crate::DEFAULT_WORKSPACE_PADDING;
@@ -60,6 +61,8 @@ pub struct Monitor {
pub container_padding: Option<i32>,
#[getset(get_copy = "pub", set = "pub")]
pub workspace_padding: Option<i32>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
pub wallpaper: Option<Wallpaper>,
}
impl_ring_elements!(Monitor, Workspace);
@@ -115,6 +118,7 @@ pub fn new(
workspace_names: HashMap::default(),
container_padding: None,
workspace_padding: None,
wallpaper: None,
}
}
@@ -156,6 +160,7 @@ impl Monitor {
workspace_names: Default::default(),
container_padding: None,
workspace_padding: None,
wallpaper: None,
}
}
@@ -178,9 +183,10 @@ impl Monitor {
pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> {
let focused_idx = self.focused_workspace_idx();
let hmonitor = self.id();
let monitor_wp = self.wallpaper.clone();
for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {
if i == focused_idx {
workspace.restore(mouse_follows_focus, hmonitor)?;
workspace.restore(mouse_follows_focus, hmonitor, &monitor_wp)?;
} else {
workspace.hide(None);
}

View File

@@ -553,6 +553,7 @@ where
workspace_names: cached.workspace_names.clone(),
container_padding: cached.container_padding,
workspace_padding: cached.workspace_padding,
wallpaper: cached.wallpaper.clone(),
};
let focused_workspace_idx = m.focused_workspace_idx();

View File

@@ -326,6 +326,9 @@ pub struct MonitorConfig {
/// Workspace padding (default: global)
#[serde(skip_serializing_if = "Option::is_none")]
pub workspace_padding: Option<i32>,
/// Specify a wallpaper for this monitor
#[serde(skip_serializing_if = "Option::is_none")]
pub wallpaper: Option<Wallpaper>,
}
impl From<&Monitor> for MonitorConfig {
@@ -361,6 +364,7 @@ impl From<&Monitor> for MonitorConfig {
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
container_padding,
workspace_padding,
wallpaper: value.wallpaper().clone(),
}
}
}
@@ -1357,6 +1361,7 @@ impl StaticConfig {
);
monitor.set_container_padding(monitor_config.container_padding);
monitor.set_workspace_padding(monitor_config.workspace_padding);
monitor.set_wallpaper(monitor_config.wallpaper.clone());
monitor.update_workspaces_globals(offset);
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {

View File

@@ -359,6 +359,7 @@ impl From<&WindowManager> for State {
workspace_names: monitor.workspace_names.clone(),
container_padding: monitor.container_padding,
workspace_padding: monitor.workspace_padding,
wallpaper: monitor.wallpaper.clone(),
})
.collect::<VecDeque<_>>();
stripped_monitors.focus(wm.monitors.focused_idx());
@@ -1015,6 +1016,7 @@ impl WindowManager {
monitor.update_workspace_globals(focused_workspace_idx, offset);
let hmonitor = monitor.id();
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?;
@@ -1026,8 +1028,8 @@ impl WindowManager {
}
}
if workspace.wallpaper().is_some() {
if let Err(error) = workspace.apply_wallpaper(hmonitor) {
if workspace.wallpaper().is_some() || monitor_wp.is_some() {
if let Err(error) = workspace.apply_wallpaper(hmonitor, &monitor_wp) {
tracing::error!("failed to apply wallpaper: {}", error);
}
}
@@ -1729,13 +1731,14 @@ impl WindowManager {
.ok_or_else(|| anyhow!("there is no monitor"))?;
let hmonitor = monitor.id();
let monitor_wp = monitor.wallpaper.clone();
let workspace = monitor
.workspaces()
.get(workspace_idx)
.ok_or_else(|| anyhow!("there is no workspace"))?;
workspace.apply_wallpaper(hmonitor)
workspace.apply_wallpaper(hmonitor, &monitor_wp)
}
pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> Result<()> {

View File

@@ -1385,4 +1385,23 @@ impl WindowsApi {
}
Ok(())
}
pub fn get_wallpaper(hmonitor: isize) -> Result<String> {
let wallpaper: IDesktopWallpaper =
unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };
let monitor_id = if let Some(path) = Self::monitor_device_path(hmonitor) {
PCWSTR::from_raw(HSTRING::from(path).as_ptr())
} else {
PCWSTR::null()
};
// Set the wallpaper
unsafe {
wallpaper
.GetWallpaper(monitor_id)
.and_then(|pwstr| pwstr.to_string().map_err(|e| e.into()))
}
.process()
}
}

View File

@@ -302,13 +302,12 @@ impl Workspace {
}
}
pub fn apply_wallpaper(&self, hmonitor: isize) -> Result<()> {
if let Some(wallpaper) = &self.wallpaper {
pub fn apply_wallpaper(&self, hmonitor: isize, monitor_wp: &Option<Wallpaper>) -> Result<()> {
if let Some(wallpaper) = self.wallpaper.as_ref().or(monitor_wp.as_ref()) {
if let Err(error) = WindowsApi::set_wallpaper(&wallpaper.path, hmonitor) {
tracing::error!("failed to set wallpaper: {error}");
}
// if !cfg!(debug_assertions) && wallpaper.generate_theme.unwrap_or(true) {
if wallpaper.generate_theme.unwrap_or(true) {
let variant = wallpaper
.theme_options
@@ -419,12 +418,17 @@ impl Workspace {
Ok(())
}
pub fn restore(&mut self, mouse_follows_focus: bool, hmonitor: isize) -> Result<()> {
pub fn restore(
&mut self,
mouse_follows_focus: bool,
hmonitor: isize,
monitor_wp: &Option<Wallpaper>,
) -> Result<()> {
if let Some(container) = self.monocle_container() {
if let Some(window) = container.focused_window() {
container.restore();
window.focus(mouse_follows_focus)?;
return self.apply_wallpaper(hmonitor);
return self.apply_wallpaper(hmonitor, monitor_wp);
}
}
@@ -468,7 +472,7 @@ impl Workspace {
floating_window.focus(mouse_follows_focus)?;
}
self.apply_wallpaper(hmonitor)
self.apply_wallpaper(hmonitor, monitor_wp)
}
pub fn update(&mut self) -> Result<()> {

View File

@@ -1131,6 +1131,257 @@
"type": "integer",
"format": "int32"
},
"wallpaper": {
"description": "Specify a wallpaper for this monitor",
"type": "object",
"required": [
"path"
],
"properties": {
"generate_theme": {
"description": "Generate and apply Base16 theme for this wallpaper (default: true)",
"type": "boolean"
},
"path": {
"description": "Path to the wallpaper image file",
"type": "string"
},
"theme_options": {
"description": "Specify Light or Dark variant for theme generation (default: Dark)",
"type": "object",
"properties": {
"bar_accent": {
"description": "Komorebi status bar accent (default: Base0D)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"floating_border": {
"description": "Border colour when the window is floating (default: Base09)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"monocle_border": {
"description": "Border colour when the container is in monocle mode (default: Base0F)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"single_border": {
"description": "Border colour when the container contains a single window (default: Base0D)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"stack_border": {
"description": "Border colour when the container contains multiple windows (default: Base0B)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"stackbar_background": {
"description": "Stackbar tab background colour (default: Base01)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"stackbar_focused_text": {
"description": "Stackbar focused tab text colour (default: Base0B)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"stackbar_unfocused_text": {
"description": "Stackbar unfocused tab text colour (default: Base05)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"theme_variant": {
"description": "Specify Light or Dark variant for theme generation (default: Dark)",
"type": "string",
"enum": [
"Dark",
"Light"
]
},
"unfocused_border": {
"description": "Border colour when the container is unfocused (default: Base01)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
},
"unfocused_locked_border": {
"description": "Border colour when the container is unfocused and locked (default: Base08)",
"type": "string",
"enum": [
"Base00",
"Base01",
"Base02",
"Base03",
"Base04",
"Base05",
"Base06",
"Base07",
"Base08",
"Base09",
"Base0A",
"Base0B",
"Base0C",
"Base0D",
"Base0E",
"Base0F"
]
}
}
}
}
},
"window_based_work_area_offset": {
"description": "Window based work area offset (default: None)",
"type": "object",