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)
|
- [ ] Switch all structs to idiomatic Zig init/deinit pattern (init returns value, caller decides stack/heap)
|
||||||
- [ ] Support per-host config using properties
|
- [ ] Support per-host config using properties
|
||||||
- [ ] Support wallpapers
|
- [ ] Support a basic bar
|
||||||
- [ ] Support a bar
|
|
||||||
- [ ] Support starting programs at WM launch
|
- [ ] Support starting programs at WM launch
|
||||||
- [ ] Support overriding config location
|
- [ ] Support overriding config location
|
||||||
- [ ] Add support for multimedia/brightness keys
|
- [ ] 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 ratio
|
||||||
- [x] Support changeable primary count
|
- [x] Support changeable primary count
|
||||||
- [x] Support multiple outputs
|
- [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
|
focus_follows_pointer #true
|
||||||
// Whether the focus should warp to the center of newly-focused windows
|
// Whether the focus should warp to the center of newly-focused windows
|
||||||
pointer_warp_on_focus_change #true
|
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 {
|
borders {
|
||||||
width 2
|
width 2
|
||||||
// 8 or 10 digit hex color
|
// 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
|
// TODO: Implement a color when this is null
|
||||||
/// Path to the wallpaper image
|
/// 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 bind entries parsed from config (tag_bind nodes in keybinds block)
|
||||||
tag_binds: std.ArrayList(Keybind) = .{},
|
tag_binds: std.ArrayList(Keybind) = .{},
|
||||||
|
|
@ -55,6 +55,7 @@ const NodeName = enum {
|
||||||
attach_mode,
|
attach_mode,
|
||||||
focus_follows_pointer,
|
focus_follows_pointer,
|
||||||
pointer_warp_on_focus_change,
|
pointer_warp_on_focus_change,
|
||||||
|
wallpaper_image_path,
|
||||||
borders,
|
borders,
|
||||||
keybinds,
|
keybinds,
|
||||||
pointer_binds,
|
pointer_binds,
|
||||||
|
|
@ -105,6 +106,9 @@ pub fn create() !*Config {
|
||||||
config.keybinds.clearAndFree(utils.allocator);
|
config.keybinds.clearAndFree(utils.allocator);
|
||||||
config.tag_binds.clearAndFree(utils.allocator);
|
config.tag_binds.clearAndFree(utils.allocator);
|
||||||
config.pointer_binds.clearAndFree(utils.allocator);
|
config.pointer_binds.clearAndFree(utils.allocator);
|
||||||
|
if (config.wallpaper_image_path) |path| {
|
||||||
|
utils.allocator.free(path);
|
||||||
|
}
|
||||||
config.* = .{};
|
config.* = .{};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +129,9 @@ pub fn destroy(config: *Config) void {
|
||||||
config.keybinds.deinit(utils.allocator);
|
config.keybinds.deinit(utils.allocator);
|
||||||
config.tag_binds.deinit(utils.allocator);
|
config.tag_binds.deinit(utils.allocator);
|
||||||
config.pointer_binds.deinit(utils.allocator);
|
config.pointer_binds.deinit(utils.allocator);
|
||||||
|
if (config.wallpaper_image_path) |path| {
|
||||||
|
utils.allocator.free(path);
|
||||||
|
}
|
||||||
utils.allocator.destroy(config);
|
utils.allocator.destroy(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,6 +186,19 @@ fn load(config: *Config, reader: *Io.Reader) !void {
|
||||||
continue;
|
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 => {
|
.borders => {
|
||||||
next_child_block = .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 {
|
fn logWarnMissingNodeArg(node_name: anytype, comptime arg: []const u8) void {
|
||||||
const node_name_type = @TypeOf(node_name);
|
const node_name_type = @TypeOf(node_name);
|
||||||
switch (node_name_type) {
|
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)}),
|
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)}),
|
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) ++ "\""),
|
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 std = @import("std");
|
||||||
const fmt = std.fmt;
|
const fmt = std.fmt;
|
||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,6 @@ pub fn create(options: Options) !*Context {
|
||||||
const context = try utils.allocator.create(Context);
|
const context = try utils.allocator.create(Context);
|
||||||
errdefer context.destroy();
|
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.* = .{
|
context.* = .{
|
||||||
.initialized = false,
|
.initialized = false,
|
||||||
.wl_compositor = options.wl_compositor,
|
.wl_compositor = options.wl_compositor,
|
||||||
|
|
@ -74,7 +68,7 @@ pub fn create(options: Options) !*Context {
|
||||||
.wl_shm = options.wl_shm,
|
.wl_shm = options.wl_shm,
|
||||||
.wl_outputs = options.wl_outputs,
|
.wl_outputs = options.wl_outputs,
|
||||||
.zwlr_layer_shell_v1 = options.zwlr_layer_shell_v1,
|
.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),
|
.wm = try WindowManager.create(context, options.river_window_manager_v1),
|
||||||
.xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1),
|
.xkb_bindings = try XkbBindings.create(context, options.river_xkb_bindings_v1),
|
||||||
.config = options.config,
|
.config = options.config,
|
||||||
|
|
@ -105,13 +99,56 @@ pub fn manage(context: *Context) void {
|
||||||
binding.link.remove();
|
binding.link.remove();
|
||||||
binding.destroy();
|
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.destroy();
|
||||||
context.config = new_config;
|
context.config = new_config;
|
||||||
context.initialized = false;
|
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 std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
|
||||||
const wayland = @import("wayland");
|
const wayland = @import("wayland");
|
||||||
const river = wayland.client.river;
|
const river = wayland.client.river;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ river_output_v1: *river.OutputV1,
|
||||||
wl_output: ?*wl.Output = null,
|
wl_output: ?*wl.Output = null,
|
||||||
|
|
||||||
// Output geometry
|
// Output geometry
|
||||||
scale: u31 = 0,
|
scale: u31 = 1,
|
||||||
width: u31 = 0,
|
width: u31 = 0,
|
||||||
height: u31 = 0,
|
height: u31 = 0,
|
||||||
x: i32 = 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
|
// 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 = output.context.wl_outputs.get(ev.name) orelse unreachable;
|
||||||
output.wl_output.?.setListener(*Output, wlOutputListener, output);
|
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| {
|
.dimensions => |ev| {
|
||||||
// Protocol guarantees that width and height are strictly greater than zero
|
// 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) {
|
if (output.context.wallpaper_image == null) {
|
||||||
// No wallpaper image, so we don't need any surfaces
|
// No wallpaper image, so we don't need any surfaces
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.wl_surface) |_| {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,18 +253,18 @@ fn initWallpaperLayerSurface(output: *Output) !void {
|
||||||
wl_surface.commit();
|
wl_surface.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinitWallpaperLayerSurface(output: *Output) void {
|
pub fn deinitWallpaperLayerSurface(output: *Output) void {
|
||||||
if (output.layer_surface) |layer_surface| {
|
if (output.layer_surface) |layer_surface| {
|
||||||
layer_surface.destroy();
|
layer_surface.destroy();
|
||||||
}
|
}
|
||||||
if (output.wl_surface) |wl_surface| {
|
if (output.wl_surface) |wl_surface| {
|
||||||
wl_surface.destroy();
|
wl_surface.destroy();
|
||||||
|
output.context.buffer_pool.surface_count -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
output.layer_surface = null;
|
output.layer_surface = null;
|
||||||
output.wl_surface = null;
|
output.wl_surface = null;
|
||||||
output.configured = false;
|
output.configured = false;
|
||||||
output.context.buffer_pool.surface_count -= 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wallpaperLayerSurfaceListener(layer_surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, output: *Output) void {
|
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
|
/// Render the wallpaper image onto the layer surface
|
||||||
fn renderWallpaper(output: *Output) !void {
|
pub fn renderWallpaper(output: *Output) !void {
|
||||||
const context = output.context;
|
const context = output.context;
|
||||||
const width = output.render_width;
|
const width = output.render_width;
|
||||||
const height = output.render_height;
|
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 std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const fatal = std.process.fatal;
|
const fatal = std.process.fatal;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue