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:
parent
00835cea08
commit
3c16929a6a
6 changed files with 100 additions and 17 deletions
|
|
@ -12,8 +12,7 @@ These are in rough order of my priority, though no promises I do them in this or
|
|||
|
||||
- [ ] Switch all structs to idiomatic Zig init/deinit pattern (init returns value, caller decides stack/heap)
|
||||
- [ ] Support per-host config using properties
|
||||
- [ ] Support wallpapers
|
||||
- [ ] Support a bar
|
||||
- [ ] Support a basic bar
|
||||
- [ ] Support starting programs at WM launch
|
||||
- [ ] Support overriding config location
|
||||
- [ ] Add support for multimedia/brightness keys
|
||||
|
|
@ -23,4 +22,5 @@ These are in rough order of my priority, though no promises I do them in this or
|
|||
- [x] Support changeable primary ratio
|
||||
- [x] Support changeable primary count
|
||||
- [x] Support multiple outputs
|
||||
- [X] Support floating windows
|
||||
- [x] Support floating windows
|
||||
- [x] Support wallpapers
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ attach_mode top
|
|||
focus_follows_pointer #true
|
||||
// Whether the focus should warp to the center of newly-focused windows
|
||||
pointer_warp_on_focus_change #true
|
||||
// Path to image to use as wallpaper
|
||||
// The same image is displayed on all outputs, but scaled separately
|
||||
// If this config is missing, then the background is blank black screen
|
||||
wallpaper_image_path "~/Pictures/the_valley.png"
|
||||
borders {
|
||||
width 2
|
||||
// 8 or 10 digit hex color
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -60,12 +60,6 @@ pub fn create(options: Options) !*Context {
|
|||
const context = try utils.allocator.create(Context);
|
||||
errdefer context.destroy();
|
||||
|
||||
// FIXME: TODO: Get this from Config
|
||||
const wallpaper_image = WallpaperImage.create("FIXME") catch |e| blk: {
|
||||
log.err("Failed to load wallpaper image from path \"{s}\": {s}", .{ "FIXME", @errorName(e) });
|
||||
break :blk null;
|
||||
};
|
||||
|
||||
context.* = .{
|
||||
.initialized = false,
|
||||
.wl_compositor = options.wl_compositor,
|
||||
|
|
@ -74,7 +68,7 @@ pub fn create(options: Options) !*Context {
|
|||
.wl_shm = options.wl_shm,
|
||||
.wl_outputs = options.wl_outputs,
|
||||
.zwlr_layer_shell_v1 = options.zwlr_layer_shell_v1,
|
||||
.wallpaper_image = wallpaper_image,
|
||||
.wallpaper_image = loadWallpaperImage(options.config),
|
||||
.wm = try WindowManager.create(context, options.river_window_manager_v1),
|
||||
.xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1),
|
||||
.config = options.config,
|
||||
|
|
@ -105,13 +99,56 @@ pub fn manage(context: *Context) void {
|
|||
binding.link.remove();
|
||||
binding.destroy();
|
||||
}
|
||||
|
||||
// Check if wallpaper path changed before destroying old config
|
||||
const wallpaper_changed = !pathsEqual(
|
||||
context.config.wallpaper_image_path,
|
||||
new_config.wallpaper_image_path,
|
||||
);
|
||||
|
||||
context.config.destroy();
|
||||
context.config = new_config;
|
||||
context.initialized = false;
|
||||
|
||||
if (wallpaper_changed) {
|
||||
if (context.wallpaper_image) |img| img.destroy();
|
||||
context.wallpaper_image = loadWallpaperImage(new_config);
|
||||
|
||||
var out_it = context.wm.outputs.iterator(.forward);
|
||||
while (out_it.next()) |output| {
|
||||
if (context.wallpaper_image == null) {
|
||||
output.deinitWallpaperLayerSurface();
|
||||
} else if (output.wl_surface != null) {
|
||||
output.renderWallpaper() catch |err| {
|
||||
log.err("Wallpaper re-render failed: {}", .{err});
|
||||
};
|
||||
} else {
|
||||
output.initWallpaperLayerSurface() catch |err| {
|
||||
log.err("Failed to init wallpaper surface: {}", .{err});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn loadWallpaperImage(config: *Config) ?*WallpaperImage {
|
||||
const image_path = config.wallpaper_image_path orelse return null;
|
||||
if (image_path.len == 0) return null;
|
||||
return WallpaperImage.create(image_path) catch |e| {
|
||||
log.err("Failed to load wallpaper image from path \"{s}\": {s}", .{ image_path, @errorName(e) });
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
fn pathsEqual(a: ?[]const u8, b: ?[]const u8) bool {
|
||||
const a_val = a orelse return b == null;
|
||||
const b_val = b orelse return false;
|
||||
return mem.eql(u8, a_val, b_val);
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const river = wayland.client.river;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ river_output_v1: *river.OutputV1,
|
|||
wl_output: ?*wl.Output = null,
|
||||
|
||||
// Output geometry
|
||||
scale: u31 = 0,
|
||||
scale: u31 = 1,
|
||||
width: u31 = 0,
|
||||
height: u31 = 0,
|
||||
x: i32 = 0,
|
||||
|
|
@ -158,6 +158,15 @@ fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.E
|
|||
// It's guaranteed for the wl_output global to advertised before this event happens
|
||||
output.wl_output = output.context.wl_outputs.get(ev.name) orelse unreachable;
|
||||
output.wl_output.?.setListener(*Output, wlOutputListener, output);
|
||||
|
||||
// The wl_output's initial events (mode, scale, name, done) were likely
|
||||
// already delivered during the initial roundtrip before we set our
|
||||
// listener, so the .done event that triggers wallpaper init was lost.
|
||||
// Explicitly init the wallpaper surface here.
|
||||
output.initWallpaperLayerSurface() catch |err| {
|
||||
const output_name = output.name orelse "some output";
|
||||
log.err("failed to add a surface to {s}: {}", .{ output_name, err });
|
||||
};
|
||||
},
|
||||
.dimensions => |ev| {
|
||||
// Protocol guarantees that width and height are strictly greater than zero
|
||||
|
|
@ -207,14 +216,14 @@ fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void
|
|||
}
|
||||
}
|
||||
|
||||
fn initWallpaperLayerSurface(output: *Output) !void {
|
||||
pub fn initWallpaperLayerSurface(output: *Output) !void {
|
||||
if (output.context.wallpaper_image == null) {
|
||||
// No wallpaper image, so we don't need any surfaces
|
||||
return;
|
||||
}
|
||||
|
||||
if (output.wl_surface) |_| {
|
||||
log.warn("Skipping adding a second wallpaper surface to {s}", .{output.name orelse "some output"});
|
||||
log.debug("Skipping adding a second wallpaper surface to {s}", .{output.name orelse "some output"});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -244,18 +253,18 @@ fn initWallpaperLayerSurface(output: *Output) !void {
|
|||
wl_surface.commit();
|
||||
}
|
||||
|
||||
fn deinitWallpaperLayerSurface(output: *Output) void {
|
||||
pub fn deinitWallpaperLayerSurface(output: *Output) void {
|
||||
if (output.layer_surface) |layer_surface| {
|
||||
layer_surface.destroy();
|
||||
}
|
||||
if (output.wl_surface) |wl_surface| {
|
||||
wl_surface.destroy();
|
||||
output.context.buffer_pool.surface_count -= 1;
|
||||
}
|
||||
|
||||
output.layer_surface = null;
|
||||
output.wl_surface = null;
|
||||
output.configured = false;
|
||||
output.context.buffer_pool.surface_count -= 1;
|
||||
}
|
||||
|
||||
fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, output: *Output) void {
|
||||
|
|
@ -314,7 +323,7 @@ fn calculate_transform(image_dimension: c_int, output_dimension: u31, dimension_
|
|||
}
|
||||
|
||||
/// Render the wallpaper image onto the layer surface
|
||||
fn renderWallpaper(output: *Output) !void {
|
||||
pub fn renderWallpaper(output: *Output) !void {
|
||||
const context = output.context;
|
||||
const width = output.render_width;
|
||||
const height = output.render_height;
|
||||
|
|
|
|||
|
|
@ -134,6 +134,10 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
|||
}
|
||||
}
|
||||
|
||||
pub const std_options = std.Options{
|
||||
.log_level = .debug,
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const fatal = std.process.fatal;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue