Begin work to add wallpaper support to beansprout

Added pixman and zigimg dependencies
Set up in build.zig, added to both exe and exe_check

Add new protocols:
     river-layer-shell-v1
     wlr-layer-shell-unstable-v1
     xdg-shell (dep of wlr-layer-shell-unstable-v1)

Update Context.zig to hold wl_output, wl_shm, and a WallpaperImage
Also re-ordered all of its fields into alphabetical order
Context.create() now takes a Context.Options struct so that it takes
     one arg instead of many smaller args.

Added new WallpaperImage.zig, but it's not yet actually used
This commit is contained in:
Ben Buhse 2026-02-06 16:37:33 -06:00
commit fb8817ebf9
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
7 changed files with 718 additions and 26 deletions

View file

@ -10,11 +10,14 @@ const Context = @This();
initialized: bool,
// Wayland globals
wl_display: *wl.Display,
wl_registry: *wl.Registry,
wl_compositor: *wl.Compositor,
wl_display: *wl.Display,
wl_output: *wl.Output,
wl_registry: *wl.Registry,
wl_shm: *wl.Shm,
// Wayland globals that we have structs for
// Wayland globals that we have special structs for
wallpaper_image: *WallpaperImage,
wm: *WindowManager,
xkb_bindings: *XkbBindings,
@ -28,25 +31,36 @@ pub const PendingManage = struct {
config: ?*Config = null,
};
pub fn create(
wl_display: *wl.Display,
wl_registry: *wl.Registry,
// I use this because otherwise create() takes
// a LOT of arguments.
pub const Options = struct {
wl_compositor: *wl.Compositor,
wl_display: *wl.Display,
wl_output: *wl.Output,
wl_registry: *wl.Registry,
wl_shm: *wl.Shm,
river_layer_shell_v1: *river.LayerShellV1,
river_window_manager_v1: *river.WindowManagerV1,
river_xkb_bindings_v1: *river.XkbBindingsV1,
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
config: *Config,
) !*Context {
};
pub fn create(options: Options) !*Context {
const context = try utils.allocator.create(Context);
errdefer context.destroy();
context.* = .{
.initialized = false,
.wl_display = wl_display,
.wl_registry = wl_registry,
.wl_compositor = wl_compositor,
.wm = try WindowManager.create(context, river_window_manager_v1),
.xkb_bindings = try XkbBindings.create(context, river_xkb_bindings_v1),
.config = config,
.wl_compositor = options.wl_compositor,
.wl_display = options.wl_display,
.wl_output = options.wl_output,
.wl_registry = options.wl_registry,
.wl_shm = options.wl_shm,
.wallpaper_image = try WallpaperImage.create("FIXME"), // FIXME: TODO: Get this from 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,
};
return context;
@ -80,9 +94,11 @@ const std = @import("std");
const wayland = @import("wayland");
const river = wayland.client.river;
const wl = wayland.client.wl;
const zwlr = wayland.client.zwlr;
const utils = @import("utils.zig");
const Config = @import("Config.zig");
const WallpaperImage = @import("WallpaperImage.zig");
const WindowManager = @import("WindowManager.zig");
const XkbBindings = @import("XkbBindings.zig");

60
src/WallpaperImage.zig Normal file
View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
//
// SPDX-License-Identifier: GPL-3.0-or-later
const WallpaperImage = @This();
image: *pixman.Image,
pixels: std.ArrayList(u32),
// TODO: Make image_path nullable, if null, do a single color with a single_pixel_buffer instead(?)
pub fn create(image_path: []const u8) !*WallpaperImage {
var wallpaper_image = try utils.allocator.create(WallpaperImage);
errdefer utils.allocator.destroy(wallpaper_image);
var read_buf: [zigimg.io.DEFAULT_BUFFER_SIZE]u8 = undefined;
var image = try zigimg.Image.fromFilePath(utils.allocator, image_path, &read_buf);
defer image.deinit(utils.allocator);
// We don't want to deal with all the possible formats,
// so let's just convert to one we can use with pixman.
if (image.pixelFormat() != .rgba32) {
try image.convert(utils.allocator, .rgba32);
}
log.debug("image loaded ({}x{})", .{ image.width, image.height });
const pixels = image.pixels.rgba32;
// We have to manually convert to argb --
// It's only guaranteed that Wayland compositors will have xrgb and argb support but zigimg doesn't have either of those.
wallpaper_image.pixels = try std.ArrayList(u32).initCapacity(utils.allocator, pixels.len);
errdefer wallpaper_image.pixels.deinit(utils.allocator);
for (0..pixels.len) |i| {
const a: u32 = @intCast(pixels[i].a);
const r: u32 = @intCast(pixels[i].r);
const g: u32 = @intCast(pixels[i].g);
const b: u32 = @intCast(pixels[i].b);
const new_val: u32 = (a << 24) + (r << 16) + (g << 8) + b;
try wallpaper_image.pixels.append(utils.allocator, new_val);
}
wallpaper_image.image = pixman.Image.createBits(.a8r8g8b8, @intCast(image.width), @intCast(image.height), @ptrCast(@alignCast(wallpaper_image.pixels.items.ptr)), @intCast(image.width * image.pixelFormat().pixelStride())) orelse return error.FailedToCreatePixmanImage;
return wallpaper_image;
}
pub fn destroy(wallpaper_image: *WallpaperImage) void {
_ = wallpaper_image.image.unref();
wallpaper_image.pixels.deinit(utils.allocator);
utils.allocator.destroy(wallpaper_image);
}
const std = @import("std");
const pixman = @import("pixman");
const zigimg = @import("zigimg");
const utils = @import("utils.zig");
const log = std.log.scoped(.WallpaperImage);

View file

@ -2,11 +2,15 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
/// Wayland globals that we need to bind to
/// Wayland globals that we need to bind listen in alphabetical order
const Globals = struct {
wl_compositor: ?*wl.Compositor = null,
river_layer_shell_v1: ?*river.LayerShellV1 = null,
river_window_manager_v1: ?*river.WindowManagerV1 = null,
river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null,
wl_compositor: ?*wl.Compositor = null,
wl_output: ?*wl.Output = null,
wl_shm: ?*wl.Shm = null,
zwlr_layer_shell_v1: ?*zwlr.LayerShellV1 = null,
};
pub fn main() !void {
@ -30,20 +34,28 @@ pub fn main() !void {
fatal("Initial roundtrip failed: E{s}", .{@tagName(errno)});
}
const wl_compositor = globals.wl_compositor orelse utils.interfaceNotAdvertised(wl.Compositor);
const river_layer_shell_v1 = globals.river_layer_shell_v1 orelse utils.interfaceNotAdvertised(river.LayerShellV1);
const river_window_manager_v1 = globals.river_window_manager_v1 orelse utils.interfaceNotAdvertised(river.WindowManagerV1);
const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse utils.interfaceNotAdvertised(river.XkbBindingsV1);
const wl_compositor = globals.wl_compositor orelse utils.interfaceNotAdvertised(wl.Compositor);
const wl_output = globals.wl_output orelse utils.interfaceNotAdvertised(wl.Output);
const wl_shm = globals.wl_shm orelse utils.interfaceNotAdvertised(wl.Shm);
const zwlr_layer_shell_v1 = globals.zwlr_layer_shell_v1 orelse utils.interfaceNotAdvertised(zwlr.LayerShellV1);
const config = try Config.create();
defer config.destroy();
const context = try Context.create(
wl_display,
wl_registry,
wl_compositor,
river_window_manager_v1,
river_xkb_bindings_v1,
config,
);
const context = try Context.create(.{
.wl_compositor = wl_compositor,
.wl_display = wl_display,
.wl_output = wl_output,
.wl_registry = wl_registry,
.wl_shm = wl_shm,
.river_layer_shell_v1 = river_layer_shell_v1,
.river_window_manager_v1 = river_window_manager_v1,
.river_xkb_bindings_v1 = river_xkb_bindings_v1,
.zwlr_layer_shell_v1 = zwlr_layer_shell_v1,
.config = config,
});
defer context.destroy();
while (true) {
@ -86,6 +98,7 @@ const process = std.process;
const wayland = @import("wayland");
const river = wayland.client.river;
const wl = wayland.client.wl;
const zwlr = wayland.client.zwlr;
const utils = @import("utils.zig");
const Config = @import("Config.zig");