Complete wallpaper support with new config

Load wallpaper_image_path from config with tilde expansion (environment
variables are not supported)

On the way for this commit, I also had to:
- Fix wallpaper not rendering on startup by triggering init from
the .wl_output handler, since wl_output.done is lost during the
initial roundtrip
- Re-render wallpapers on config reload when the path changes
- Fix crash in deinitWallpaperLayerSurface when wl_surface is null
This commit is contained in:
Ben Buhse 2026-02-07 19:11:10 -06:00
commit 3c16929a6a
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
6 changed files with 100 additions and 17 deletions

View file

@ -22,7 +22,7 @@ pointer_warp_on_focus_change: bool = true,
// TODO: Implement a color when this is null
/// Path to the wallpaper image
wallpaper_image_path: []const u8 = "",
wallpaper_image_path: ?[]const u8 = null,
/// Tag bind entries parsed from config (tag_bind nodes in keybinds block)
tag_binds: std.ArrayList(Keybind) = .{},
@ -55,6 +55,7 @@ const NodeName = enum {
attach_mode,
focus_follows_pointer,
pointer_warp_on_focus_change,
wallpaper_image_path,
borders,
keybinds,
pointer_binds,
@ -105,6 +106,9 @@ pub fn create() !*Config {
config.keybinds.clearAndFree(utils.allocator);
config.tag_binds.clearAndFree(utils.allocator);
config.pointer_binds.clearAndFree(utils.allocator);
if (config.wallpaper_image_path) |path| {
utils.allocator.free(path);
}
config.* = .{};
};
}
@ -125,6 +129,9 @@ pub fn destroy(config: *Config) void {
config.keybinds.deinit(utils.allocator);
config.tag_binds.deinit(utils.allocator);
config.pointer_binds.deinit(utils.allocator);
if (config.wallpaper_image_path) |path| {
utils.allocator.free(path);
}
utils.allocator.destroy(config);
}
@ -179,6 +186,19 @@ fn load(config: *Config, reader: *Io.Reader) !void {
continue;
}
},
.wallpaper_image_path => {
if (node.argcount() < 1) {
logWarnMissingNodeArg(name, "image path");
continue;
}
const path_str = utils.stripQuotes(node.arg(&parser, 0) orelse unreachable);
config.wallpaper_image_path = expandTilde(path_str) catch {
logWarnInvalidNodeArg(name, path_str);
continue;
};
logDebugSettingNode(name, path_str);
},
.borders => {
next_child_block = .borders;
},
@ -547,6 +567,7 @@ fn logWarnInvalidNodeArg(node_name: anytype, node_value: []const u8) void {
fn logWarnMissingNodeArg(node_name: anytype, comptime arg: []const u8) void {
const node_name_type = @TypeOf(node_name);
switch (node_name_type) {
NodeName => log.warn("\"{s}\" missing " ++ arg ++ " argument. Ignoring", .{@tagName(node_name)}),
KeybindNodeName => log.warn("\"keybind.{s}\" missing " ++ arg ++ " argument. Ignoring", .{@tagName(node_name)}),
PointerBindNodeName => log.warn("\"pointer_binds.{s}\" missing " ++ arg ++ " argument. Ignoring", .{@tagName(node_name)}),
else => @compileError("This function does not (yet) support type \"" ++ @typeName(node_name_type) ++ "\""),
@ -574,6 +595,14 @@ fn logDebugSettingNode(node_name: anytype, node_value: []const u8) void {
}
}
fn expandTilde(path: []const u8) ![]const u8 {
if (path.len > 0 and path[0] == '~') {
const home = std.posix.getenv("HOME") orelse return error.HomeNotSet;
return std.fmt.allocPrint(utils.allocator, "{s}{s}", .{ home, path[1..] });
}
return utils.allocator.dupe(u8, path);
}
const std = @import("std");
const fmt = std.fmt;
const fs = std.fs;