Implement wallpaper rendering with multi-output support
This actually renders a wallpaper for each output using the newly added Buffer and BufferPool for shared-memory surfaces and creates a wlr-layer-shell surface per output. Right now, each wallpaper shares the same wallpaper (though scaled to each). wl_output globals get added to a HashMap that is used by Output when it gets an output event. Fix null-safety in WindowManager when no seats/outputs exist and route Window dimensions through pending_manage.
This commit is contained in:
parent
fb8817ebf9
commit
e186a2d017
9 changed files with 568 additions and 35 deletions
|
|
@ -10,6 +10,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
These are in rough order of my priority, though no promises I do them in this order.
|
These are in rough order of my priority, though no promises I do them in this order.
|
||||||
|
|
||||||
|
- [ ] 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 wallpapers
|
||||||
- [ ] Support a bar
|
- [ ] Support a bar
|
||||||
- [ ] Support starting programs at WM launch
|
- [ ] Support starting programs at WM launch
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,6 @@ pub fn build(b: *std.Build) void {
|
||||||
exe_check.linkSystemLibrary("pixman-1");
|
exe_check.linkSystemLibrary("pixman-1");
|
||||||
exe_check.linkSystemLibrary("xkbcommon");
|
exe_check.linkSystemLibrary("xkbcommon");
|
||||||
|
|
||||||
const check = b.step("check", "Check if beanbag compiles");
|
const check = b.step("check", "Check if beansprout compiles");
|
||||||
check.dependOn(&exe_check.step);
|
check.dependOn(&exe_check.step);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
128
src/Buffer.zig
Normal file
128
src/Buffer.zig
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
const Buffer = @This();
|
||||||
|
|
||||||
|
width: u31,
|
||||||
|
height: u31,
|
||||||
|
stride: u31,
|
||||||
|
|
||||||
|
busy: bool,
|
||||||
|
size: u31,
|
||||||
|
data: ?[]align(std.heap.page_size_min) u8,
|
||||||
|
|
||||||
|
wl_buffer: *wl.Buffer,
|
||||||
|
pixman_image: *pixman.Image,
|
||||||
|
|
||||||
|
/// Used to add Buffers to a BufferPool
|
||||||
|
node: std.DoublyLinkedList.Node = .{},
|
||||||
|
|
||||||
|
pub fn init(shm: *wl.Shm, width: u31, height: u31) !Buffer {
|
||||||
|
// We use argb8888
|
||||||
|
const stride = width * 4;
|
||||||
|
const size: u31 = height * stride;
|
||||||
|
|
||||||
|
log.debug("initializing a new buffer with size {d}", .{size});
|
||||||
|
|
||||||
|
// Open a memory-backed file with sealing enabled
|
||||||
|
const fd = switch (builtin.target.os.tag) {
|
||||||
|
.linux => try posix.memfd_createZ("beansprout-shm-buffer", os.linux.MFD.CLOEXEC | os.linux.MFD.ALLOW_SEALING),
|
||||||
|
.freebsd => try posix.memfd_createZ("beansprout-shm-buffer", std.c.MFD.CLOEXEC | std.c.MFD.ALLOW_SEALING),
|
||||||
|
else => @compileError("target OS not supported"),
|
||||||
|
};
|
||||||
|
defer posix.close(fd);
|
||||||
|
|
||||||
|
// Try to allocate it to the desired size
|
||||||
|
try posix.ftruncate(fd, size);
|
||||||
|
|
||||||
|
// mmap the memory file for the pixman image
|
||||||
|
const data = mem.bytesAsSlice(
|
||||||
|
u8,
|
||||||
|
try posix.mmap(null, size, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0),
|
||||||
|
);
|
||||||
|
errdefer posix.munmap(data);
|
||||||
|
|
||||||
|
// Seal the fd to prevent size changes. The compositor maps the same fd,
|
||||||
|
// so without sealing it could access invalid memory if the client resized it.
|
||||||
|
_ = try posix.fcntl(fd, seal.F_ADD_SEALS, seal.SEAL_GROW | seal.SEAL_SHRINK | seal.SEAL_SEAL);
|
||||||
|
|
||||||
|
// Create a Wayland shm buffer for the same memory file.
|
||||||
|
const pool = try shm.createPool(fd, size);
|
||||||
|
defer pool.destroy();
|
||||||
|
|
||||||
|
const wl_buffer = try pool.createBuffer(0, width, height, stride, .argb8888);
|
||||||
|
errdefer wl_buffer.destroy();
|
||||||
|
|
||||||
|
// Create the pixman image.
|
||||||
|
const pixman_image = pixman.Image.createBitsNoClear(
|
||||||
|
.a8r8g8b8,
|
||||||
|
@as(c_int, @intCast(width)),
|
||||||
|
@as(c_int, @intCast(height)),
|
||||||
|
@as([*c]u32, @ptrCast(data)),
|
||||||
|
@as(c_int, @intCast(stride)),
|
||||||
|
) orelse return error.NoPixmanImage;
|
||||||
|
|
||||||
|
// The pixman image and the Wayland buffer now share the same memory.
|
||||||
|
return .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.stride = stride,
|
||||||
|
.busy = true,
|
||||||
|
.size = size,
|
||||||
|
.wl_buffer = wl_buffer,
|
||||||
|
.data = data,
|
||||||
|
.pixman_image = pixman_image,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(buffer: *Buffer) void {
|
||||||
|
_ = buffer.pixman_image.unref();
|
||||||
|
buffer.wl_buffer.destroy();
|
||||||
|
if (buffer.data) |data| posix.munmap(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to do this later because of the way init() works
|
||||||
|
pub fn setListener(buffer: *Buffer) void {
|
||||||
|
buffer.wl_buffer.setListener(*Buffer, buffer_listener, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void {
|
||||||
|
switch (event) {
|
||||||
|
.release => buffer.busy = false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const mem = std.mem;
|
||||||
|
const os = std.os;
|
||||||
|
const posix = std.posix;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.client.wl;
|
||||||
|
const pixman = @import("pixman");
|
||||||
|
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
|
||||||
|
/// Sealing constants for memfd. Prevents the compositor from accessing
|
||||||
|
/// invalid memory by locking the fd's size after setup.
|
||||||
|
const seal = switch (builtin.target.os.tag) {
|
||||||
|
.linux => struct {
|
||||||
|
// Linux values are missing from stdlib right now,
|
||||||
|
// just take the values from fcntl.h
|
||||||
|
const F_ADD_SEALS: i32 = 1033;
|
||||||
|
const SEAL_SEAL: usize = 0x0001;
|
||||||
|
const SEAL_SHRINK: usize = 0x0002;
|
||||||
|
const SEAL_GROW: usize = 0x0004;
|
||||||
|
},
|
||||||
|
.freebsd => struct {
|
||||||
|
const F_ADD_SEALS = std.c.F.ADD_SEALS;
|
||||||
|
const SEAL_SEAL = std.c.F.SEAL_SEAL;
|
||||||
|
const SEAL_SHRINK = std.c.F.SEAL_SHRINK;
|
||||||
|
const SEAL_GROW = std.c.F.SEAL_GROW;
|
||||||
|
},
|
||||||
|
else => @compileError("target OS not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const log = std.log.scoped(.Buffer);
|
||||||
122
src/BufferPool.zig
Normal file
122
src/BufferPool.zig
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// Borrowed and adapted from https://git.sr.ht/~leon_plickat/wayprompt
|
||||||
|
const BufferPool = @This();
|
||||||
|
|
||||||
|
/// The amount of buffers per surface we consider the reasonable upper limit.
|
||||||
|
/// Some compositors sometimes tripple-buffer, so three seems to be ok.
|
||||||
|
/// Note that we can absolutely work with higher buffer numbers if needed,
|
||||||
|
/// however we consider that to be an anomaly and therefore do not want to
|
||||||
|
/// keep all those extra buffers around if we can avoid it, as to not have
|
||||||
|
/// unecessary memory overhead.
|
||||||
|
const max_buffer_multiplicity = 3;
|
||||||
|
|
||||||
|
/// The buffers. This is a linked list and not an array list, because we
|
||||||
|
/// need stable pointers for the listener of the wl_buffer object.
|
||||||
|
buffers: DoublyLinkedList = .{},
|
||||||
|
len: usize = 0,
|
||||||
|
|
||||||
|
/// Number of surfaces sharing this pool, used to determine when to cull extra buffers.
|
||||||
|
/// Each surface is allowed up to max_buffer_multiplicity buffers.
|
||||||
|
surface_count: usize = 0,
|
||||||
|
|
||||||
|
/// Deinit the buffer pool, destroying all buffers and freeing all memory.
|
||||||
|
pub fn deinit(buffer_pool: *BufferPool) void {
|
||||||
|
var it = buffer_pool.buffers.first;
|
||||||
|
while (it) |node| {
|
||||||
|
// Advance before destroying, since node is embedded in buffer
|
||||||
|
it = node.next;
|
||||||
|
const buffer: *Buffer = @fieldParentPtr("node", node);
|
||||||
|
buffer.deinit();
|
||||||
|
utils.allocator.destroy(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a buffer with the specified dimensions. If possible, an idle buffer is
|
||||||
|
/// reused, otherwise a new one is created.
|
||||||
|
pub fn nextBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !*Buffer {
|
||||||
|
log.debug("looking for buffer with dimensions {}x{}, total existing buffers: {}", .{ width, height, buffer_pool.len });
|
||||||
|
defer {
|
||||||
|
// Clear up extra buffers
|
||||||
|
if (buffer_pool.len > max_buffer_multiplicity * buffer_pool.surface_count) {
|
||||||
|
buffer_pool.cullBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (try buffer_pool.findSuitableBuffer(wl_shm, width, height)) |buffer| {
|
||||||
|
return buffer;
|
||||||
|
} else {
|
||||||
|
return try buffer_pool.newBuffer(wl_shm, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the first free buffer with the specified dimensions.
|
||||||
|
/// If there are no free buffers with the right dimensions, re-init a free buffer that
|
||||||
|
/// has other dimensions. If no free buffer exists at all, return null.
|
||||||
|
fn findSuitableBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !?*Buffer {
|
||||||
|
var it = buffer_pool.buffers.first;
|
||||||
|
var first_unbusy_buffer: ?*Buffer = null;
|
||||||
|
while (it) |node| : (it = node.next) {
|
||||||
|
const buffer: *Buffer = @fieldParentPtr("node", node);
|
||||||
|
if (buffer.busy) continue;
|
||||||
|
if (buffer.width == width and buffer.height == height) {
|
||||||
|
return buffer;
|
||||||
|
} else {
|
||||||
|
first_unbusy_buffer = buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No buffer has matching dimensions, however we do have an unbusy
|
||||||
|
// buffer which we can just re-init.
|
||||||
|
if (first_unbusy_buffer) |buffer| {
|
||||||
|
buffer.deinit();
|
||||||
|
buffer.* = try Buffer.init(wl_shm, width, height);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newBuffer(buffer_pool: *BufferPool, wl_shm: *wl.Shm, width: u31, height: u31) !*Buffer {
|
||||||
|
log.debug("creating new buffer {}x{}", .{ width, height });
|
||||||
|
const buffer = try utils.allocator.create(Buffer);
|
||||||
|
errdefer utils.allocator.destroy(buffer);
|
||||||
|
buffer.* = try Buffer.init(wl_shm, width, height);
|
||||||
|
buffer.setListener();
|
||||||
|
buffer_pool.buffers.append(&buffer.node);
|
||||||
|
buffer_pool.len += 1;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cullBuffers(buffer_pool: *BufferPool) void {
|
||||||
|
log.debug("culling extra buffers", .{});
|
||||||
|
var overhead = buffer_pool.len - max_buffer_multiplicity * buffer_pool.surface_count;
|
||||||
|
var it = buffer_pool.buffers.first;
|
||||||
|
while (it) |node| {
|
||||||
|
if (overhead == 0) break;
|
||||||
|
// Advance before destroying, since node is embedded in buffer
|
||||||
|
it = node.next;
|
||||||
|
const buffer: *Buffer = @fieldParentPtr("node", node);
|
||||||
|
if (!buffer.busy) {
|
||||||
|
buffer.deinit();
|
||||||
|
buffer_pool.buffers.remove(node);
|
||||||
|
utils.allocator.destroy(buffer);
|
||||||
|
buffer_pool.len -= 1;
|
||||||
|
overhead -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug(" -> new buffer count: {}", .{buffer_pool.len});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const DoublyLinkedList = std.DoublyLinkedList;
|
||||||
|
|
||||||
|
const wayland = @import("wayland");
|
||||||
|
const wl = wayland.client.wl;
|
||||||
|
|
||||||
|
const Buffer = @import("Buffer.zig");
|
||||||
|
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.BufferPool);
|
||||||
|
|
@ -12,15 +12,23 @@ initialized: bool,
|
||||||
// Wayland globals
|
// Wayland globals
|
||||||
wl_compositor: *wl.Compositor,
|
wl_compositor: *wl.Compositor,
|
||||||
wl_display: *wl.Display,
|
wl_display: *wl.Display,
|
||||||
wl_output: *wl.Output,
|
|
||||||
wl_registry: *wl.Registry,
|
wl_registry: *wl.Registry,
|
||||||
wl_shm: *wl.Shm,
|
wl_shm: *wl.Shm,
|
||||||
|
wl_outputs: *std.AutoHashMapUnmanaged(u32, *wl.Output),
|
||||||
|
|
||||||
|
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
||||||
|
|
||||||
// Wayland globals that we have special structs for
|
// Wayland globals that we have special structs for
|
||||||
wallpaper_image: *WallpaperImage,
|
|
||||||
wm: *WindowManager,
|
wm: *WindowManager,
|
||||||
xkb_bindings: *XkbBindings,
|
xkb_bindings: *XkbBindings,
|
||||||
|
|
||||||
|
/// Pool of Buffers used for rendering wallpapers
|
||||||
|
buffer_pool: BufferPool = .{},
|
||||||
|
|
||||||
|
/// Holds a pixman.Image (and its raw pixels) for the wallpaper
|
||||||
|
/// (same image on all outputs, but scaled separately)
|
||||||
|
wallpaper_image: *WallpaperImage,
|
||||||
|
|
||||||
// WM Configuration
|
// WM Configuration
|
||||||
config: *Config,
|
config: *Config,
|
||||||
|
|
||||||
|
|
@ -36,12 +44,14 @@ pub const PendingManage = struct {
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
wl_compositor: *wl.Compositor,
|
wl_compositor: *wl.Compositor,
|
||||||
wl_display: *wl.Display,
|
wl_display: *wl.Display,
|
||||||
wl_output: *wl.Output,
|
|
||||||
wl_registry: *wl.Registry,
|
wl_registry: *wl.Registry,
|
||||||
wl_shm: *wl.Shm,
|
wl_shm: *wl.Shm,
|
||||||
river_layer_shell_v1: *river.LayerShellV1,
|
wl_outputs: *std.AutoHashMapUnmanaged(u32, *wl.Output),
|
||||||
|
|
||||||
|
river_layer_shell_v1: *river.LayerShellV1, // TODO
|
||||||
river_window_manager_v1: *river.WindowManagerV1,
|
river_window_manager_v1: *river.WindowManagerV1,
|
||||||
river_xkb_bindings_v1: *river.XkbBindingsV1,
|
river_xkb_bindings_v1: *river.XkbBindingsV1,
|
||||||
|
|
||||||
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
zwlr_layer_shell_v1: *zwlr.LayerShellV1,
|
||||||
config: *Config,
|
config: *Config,
|
||||||
};
|
};
|
||||||
|
|
@ -54,9 +64,10 @@ pub fn create(options: Options) !*Context {
|
||||||
.initialized = false,
|
.initialized = false,
|
||||||
.wl_compositor = options.wl_compositor,
|
.wl_compositor = options.wl_compositor,
|
||||||
.wl_display = options.wl_display,
|
.wl_display = options.wl_display,
|
||||||
.wl_output = options.wl_output,
|
|
||||||
.wl_registry = options.wl_registry,
|
.wl_registry = options.wl_registry,
|
||||||
.wl_shm = options.wl_shm,
|
.wl_shm = options.wl_shm,
|
||||||
|
.wl_outputs = options.wl_outputs,
|
||||||
|
.zwlr_layer_shell_v1 = options.zwlr_layer_shell_v1,
|
||||||
.wallpaper_image = try WallpaperImage.create("FIXME"), // FIXME: TODO: Get this from Config
|
.wallpaper_image = try WallpaperImage.create("FIXME"), // FIXME: TODO: Get this from 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),
|
||||||
|
|
@ -70,6 +81,9 @@ pub fn destroy(context: *Context) void {
|
||||||
context.xkb_bindings.destroy();
|
context.xkb_bindings.destroy();
|
||||||
context.wm.destroy();
|
context.wm.destroy();
|
||||||
|
|
||||||
|
context.wallpaper_image.destroy();
|
||||||
|
context.buffer_pool.deinit();
|
||||||
|
|
||||||
utils.allocator.destroy(context);
|
utils.allocator.destroy(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +112,7 @@ const zwlr = wayland.client.zwlr;
|
||||||
|
|
||||||
const utils = @import("utils.zig");
|
const utils = @import("utils.zig");
|
||||||
const Config = @import("Config.zig");
|
const Config = @import("Config.zig");
|
||||||
|
const BufferPool = @import("BufferPool.zig");
|
||||||
const WallpaperImage = @import("WallpaperImage.zig");
|
const WallpaperImage = @import("WallpaperImage.zig");
|
||||||
const WindowManager = @import("WindowManager.zig");
|
const WindowManager = @import("WindowManager.zig");
|
||||||
const XkbBindings = @import("XkbBindings.zig");
|
const XkbBindings = @import("XkbBindings.zig");
|
||||||
|
|
|
||||||
245
src/Output.zig
245
src/Output.zig
|
|
@ -8,11 +8,22 @@ context: *Context,
|
||||||
|
|
||||||
river_output_v1: *river.OutputV1,
|
river_output_v1: *river.OutputV1,
|
||||||
|
|
||||||
width: i32 = 0,
|
// We have to wait for the rwm.wl_output event to get this
|
||||||
height: i32 = 0,
|
wl_output: ?*wl.Output = null,
|
||||||
|
|
||||||
|
// Output geometry
|
||||||
|
scale: u31 = 0,
|
||||||
|
width: u31 = 0,
|
||||||
|
height: u31 = 0,
|
||||||
x: i32 = 0,
|
x: i32 = 0,
|
||||||
y: i32 = 0,
|
y: i32 = 0,
|
||||||
|
|
||||||
|
// Information for this Output's wallpaper
|
||||||
|
render_width: u31 = 0,
|
||||||
|
render_height: u31 = 0,
|
||||||
|
wl_surface: ?*wl.Surface = null,
|
||||||
|
layer_surface: ?*zwlr.LayerSurfaceV1 = null,
|
||||||
|
|
||||||
/// Proportion of output width taken by the primary stack
|
/// Proportion of output width taken by the primary stack
|
||||||
primary_ratio: f32 = 0.55,
|
primary_ratio: f32 = 0.55,
|
||||||
|
|
||||||
|
|
@ -25,13 +36,19 @@ tags: u32 = 0x0001,
|
||||||
/// State consumed in manage() phase, reset at end of manage().
|
/// State consumed in manage() phase, reset at end of manage().
|
||||||
pending_manage: PendingManage = .{},
|
pending_manage: PendingManage = .{},
|
||||||
|
|
||||||
|
// Friendly name of this output
|
||||||
|
name: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// Used for wallpaper rendering management
|
||||||
|
configured: bool = false,
|
||||||
|
|
||||||
windows: wl.list.Head(Window, .link),
|
windows: wl.list.Head(Window, .link),
|
||||||
|
|
||||||
link: wl.list.Link,
|
link: wl.list.Link,
|
||||||
|
|
||||||
pub const PendingManage = struct {
|
pub const PendingManage = struct {
|
||||||
width: ?i32 = null,
|
width: ?u31 = null,
|
||||||
height: ?i32 = null,
|
height: ?u31 = null,
|
||||||
x: ?i32 = null,
|
x: ?i32 = null,
|
||||||
y: ?i32 = null,
|
y: ?i32 = null,
|
||||||
|
|
||||||
|
|
@ -53,7 +70,7 @@ pub fn create(context: *Context, river_output_v1: *river.OutputV1) !*Output {
|
||||||
|
|
||||||
output.windows.init();
|
output.windows.init();
|
||||||
|
|
||||||
output.river_output_v1.setListener(*Output, outputListener, output);
|
output.river_output_v1.setListener(*Output, riverOutputListener, output);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
@ -65,6 +82,7 @@ pub fn destroy(output: *Output) void {
|
||||||
window.destroy();
|
window.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output.deinitWallpaperLayerSurface();
|
||||||
output.river_output_v1.destroy();
|
output.river_output_v1.destroy();
|
||||||
utils.allocator.destroy(output);
|
utils.allocator.destroy(output);
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +103,8 @@ pub fn prevWindow(output: *Output, current: *Window) ?*Window {
|
||||||
return @fieldParentPtr("link", prev_link);
|
return @fieldParentPtr("link", prev_link);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void {
|
// Used for the river_output_v1 interface
|
||||||
|
fn riverOutputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event, output: *Output) void {
|
||||||
assert(output.river_output_v1 == river_output_v1);
|
assert(output.river_output_v1 == river_output_v1);
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.removed => {
|
.removed => {
|
||||||
|
|
@ -136,21 +155,220 @@ fn outputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event,
|
||||||
output.destroy();
|
output.destroy();
|
||||||
},
|
},
|
||||||
.wl_output => |ev| {
|
.wl_output => |ev| {
|
||||||
log.debug("initializing new river_output_v1 corresponding to wl_output: {d}", .{ev.name});
|
// 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);
|
||||||
},
|
},
|
||||||
.dimensions => |ev| {
|
.dimensions => |ev| {
|
||||||
output.pending_manage.width = ev.width;
|
// Protocol guarantees that width and height are strictly greater than zero
|
||||||
output.pending_manage.height = ev.height;
|
assert(ev.width > 0 and ev.height > 0);
|
||||||
output.context.wm.river_window_manager_v1.manageDirty();
|
output.pending_manage.width = @intCast(ev.width);
|
||||||
|
output.pending_manage.height = @intCast(ev.height);
|
||||||
},
|
},
|
||||||
.position => |ev| {
|
.position => |ev| {
|
||||||
output.pending_manage.x = ev.x;
|
output.pending_manage.x = ev.x;
|
||||||
output.pending_manage.y = ev.y;
|
output.pending_manage.y = ev.y;
|
||||||
output.context.wm.river_window_manager_v1.manageDirty();
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for the wl_output global interface that corresponds to the river_output_v1
|
||||||
|
fn wlOutputListener(_: *wl.Output, event: wl.Output.Event, output: *Output) void {
|
||||||
|
switch (event) {
|
||||||
|
.mode => |ev| {
|
||||||
|
if (ev.width < 0 or ev.height < 0) {
|
||||||
|
// I'm not actually sure if this is possible, but just to be safe
|
||||||
|
log.warn("Received wl_output.mode event with a negative width or height ({d}x{d})", .{ ev.width, ev.height });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.width = @intCast(ev.width);
|
||||||
|
output.height = @intCast(ev.height);
|
||||||
|
},
|
||||||
|
.done => {
|
||||||
|
output.initWallpaperLayerSurface() catch |err| {
|
||||||
|
const output_name = output.name orelse "some output";
|
||||||
|
log.err("failed to add a surface to {s}: {}", .{ output_name, err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.scale => |ev| {
|
||||||
|
if (ev.factor < 0) {
|
||||||
|
// I'm not actually sure if this is possible, but just to be safe
|
||||||
|
log.warn("Received wl_output.scale event with a negative factor ({d})", .{ev.factor});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output.scale = @intCast(ev.factor);
|
||||||
|
},
|
||||||
|
.name => |ev| {
|
||||||
|
output.name = utils.allocator.dupe(u8, mem.span(ev.name)) catch @panic("Out of memory");
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initWallpaperLayerSurface(output: *Output) !void {
|
||||||
|
if (output.wl_surface) |_| {
|
||||||
|
log.warn("Skipping adding a second wallpaper surface to {s}", .{output.name orelse "some output"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = output.context;
|
||||||
|
|
||||||
|
const wl_surface: *wl.Surface = try context.wl_compositor.createSurface();
|
||||||
|
|
||||||
|
// We don't want our surface to have any input region (default is infinite)
|
||||||
|
const empty_region: *wl.Region = try context.wl_compositor.createRegion();
|
||||||
|
defer empty_region.destroy();
|
||||||
|
wl_surface.setInputRegion(empty_region);
|
||||||
|
|
||||||
|
// Full surface should be opaque
|
||||||
|
const opaque_region: *wl.Region = try context.wl_compositor.createRegion();
|
||||||
|
defer opaque_region.destroy();
|
||||||
|
wl_surface.setOpaqueRegion(opaque_region);
|
||||||
|
|
||||||
|
const layer_surface: *zwlr.LayerSurfaceV1 = try context.zwlr_layer_shell_v1.getLayerSurface(wl_surface, output.wl_output, .background, "beansprout");
|
||||||
|
layer_surface.setExclusiveZone(-1);
|
||||||
|
layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = true, .left = true });
|
||||||
|
|
||||||
|
output.wl_surface = wl_surface;
|
||||||
|
output.layer_surface = layer_surface;
|
||||||
|
context.buffer_pool.surface_count += 1;
|
||||||
|
|
||||||
|
layer_surface.setListener(*Output, wallpaperLayerSurfaceListener, output);
|
||||||
|
wl_surface.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinitWallpaperLayerSurface(output: *Output) void {
|
||||||
|
if (output.layer_surface) |layer_surface| {
|
||||||
|
layer_surface.destroy();
|
||||||
|
}
|
||||||
|
if (output.wl_surface) |wl_surface| {
|
||||||
|
wl_surface.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
switch (event) {
|
||||||
|
.configure => |ev| {
|
||||||
|
layer_surface.ackConfigure(ev.serial);
|
||||||
|
|
||||||
|
if (ev.width < 0 or ev.height < 0) {
|
||||||
|
// I'm not actually sure if this is possible, but just to be safe
|
||||||
|
log.warn("Received zwlr_layer_surface_v1.configure event with a negative width or height ({d}x{d})", .{ ev.width, ev.height });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const width: u31 = @intCast(ev.width);
|
||||||
|
const height: u31 = @intCast(ev.height);
|
||||||
|
|
||||||
|
if (output.configured and output.render_width == width and output.render_height == height) {
|
||||||
|
if (output.wl_surface) |wl_surface| {
|
||||||
|
wl_surface.commit();
|
||||||
|
} else {
|
||||||
|
log.warn("Output is marked as configured but is missing a layer_surface for the wallpaper", .{});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("configuring wallpaper surface with width {} and height {}", .{ width, height });
|
||||||
|
output.render_width = width;
|
||||||
|
output.render_height = height;
|
||||||
|
output.configured = true;
|
||||||
|
|
||||||
|
output.renderWallpaper() catch |err| {
|
||||||
|
fatal("Wallpaper render failed: E{}", .{err});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.closed => {
|
||||||
|
output.deinitWallpaperLayerSurface();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates image_dimension / (output_dimension * scale)
|
||||||
|
fn calculate_scale(image_dimension: c_int, output_dimension: u31, scale: u31) f64 {
|
||||||
|
const numerator: f64 = @floatFromInt(image_dimension);
|
||||||
|
const denominator: f64 = @floatFromInt(output_dimension * scale);
|
||||||
|
|
||||||
|
return numerator / denominator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates (image_dimension / dimension_scale - output_dimension) / 2 / dimension_scale;
|
||||||
|
fn calculate_transform(image_dimension: c_int, output_dimension: u31, dimension_scale: f64) f64 {
|
||||||
|
const numerator1: f64 = @floatFromInt(image_dimension);
|
||||||
|
const denominator1: f64 = dimension_scale;
|
||||||
|
const subtruend: f64 = @floatFromInt(output_dimension);
|
||||||
|
const numerator2: f64 = numerator1 / denominator1 - subtruend;
|
||||||
|
|
||||||
|
return numerator2 / 2 / dimension_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the wallpaper image onto the layer surface
|
||||||
|
fn renderWallpaper(output: *Output) !void {
|
||||||
|
const context = output.context;
|
||||||
|
const width = output.render_width;
|
||||||
|
const height = output.render_height;
|
||||||
|
const scale = output.scale;
|
||||||
|
|
||||||
|
// Don't have anything to render
|
||||||
|
if (width == 0 or height == 0 or scale == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buffer: *Buffer = try context.buffer_pool.nextBuffer(context.wl_shm, width * scale, height * scale);
|
||||||
|
|
||||||
|
// Scale our loaded image and then copy it into the Buffer's pixman.Image
|
||||||
|
const image = context.wallpaper_image.image;
|
||||||
|
const image_data = image.getData();
|
||||||
|
const image_width = image.getWidth();
|
||||||
|
const image_height = image.getHeight();
|
||||||
|
const image_stride = image.getStride();
|
||||||
|
const image_format = image.getFormat();
|
||||||
|
|
||||||
|
const pix = pixman.Image.createBitsNoClear(image_format, image_width, image_height, image_data, image_stride);
|
||||||
|
if (pix == null) {
|
||||||
|
log.err("failed to copy the background image for rendering", .{});
|
||||||
|
return error.ImageCopyError;
|
||||||
|
}
|
||||||
|
defer _ = pix.?.unref();
|
||||||
|
|
||||||
|
// Get scale for our image compared to the monitor's scale
|
||||||
|
// XXX: This sucks in Zig but also I'm sure there's a better way to write it
|
||||||
|
var sx: f64 = @as(f64, @floatFromInt(image_width)) / @as(f64, @floatFromInt(width * scale));
|
||||||
|
var sy: f64 = calculate_scale(image_height, height, scale);
|
||||||
|
|
||||||
|
const s = if (sx > sy) sy else sx;
|
||||||
|
sx = s;
|
||||||
|
sy = s;
|
||||||
|
|
||||||
|
const tx: f64 = calculate_transform(image_width, width, sx);
|
||||||
|
const ty: f64 = calculate_transform(image_height, height, sy);
|
||||||
|
|
||||||
|
var t: pixman.FTransform = undefined;
|
||||||
|
var t2: pixman.Transform = undefined;
|
||||||
|
|
||||||
|
pixman.FTransform.initTranslate(&t, tx, ty);
|
||||||
|
pixman.FTransform.initScale(&t, sx, sy);
|
||||||
|
_ = pixman.Transform.fromFTransform(&t2, &t);
|
||||||
|
_ = pix.?.setTransform(&t2);
|
||||||
|
_ = pix.?.setFilter(.best, &[_]pixman.Fixed{}, 0);
|
||||||
|
|
||||||
|
pixman.Image.composite32(.src, pix.?, null, buffer.pixman_image, 0, 0, 0, 0, 0, 0, width * scale, height * scale);
|
||||||
|
|
||||||
|
log.info("render: {}x{} (scaled from {}x{})", .{ width * scale, height * scale, image_width, image_height });
|
||||||
|
|
||||||
|
// Attach the buffer to the surface
|
||||||
|
const wl_surface = output.wl_surface.?;
|
||||||
|
wl_surface.setBufferScale(scale);
|
||||||
|
wl_surface.attach(buffer.wl_buffer, 0, 0);
|
||||||
|
wl_surface.damageBuffer(0, 0, width * scale, height * scale);
|
||||||
|
wl_surface.commit();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn manage(output: *Output) void {
|
pub fn manage(output: *Output) void {
|
||||||
defer output.pending_manage = .{};
|
defer output.pending_manage = .{};
|
||||||
|
|
||||||
|
|
@ -316,13 +534,18 @@ fn calculatePrimaryStackLayout(output: *Output) void {
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const fatal = std.process.fatal;
|
||||||
|
const mem = std.mem;
|
||||||
const DoublyLinkedList = std.DoublyLinkedList;
|
const DoublyLinkedList = std.DoublyLinkedList;
|
||||||
|
|
||||||
const wayland = @import("wayland");
|
const wayland = @import("wayland");
|
||||||
const wl = wayland.client.wl;
|
const wl = wayland.client.wl;
|
||||||
const river = wayland.client.river;
|
const river = wayland.client.river;
|
||||||
|
const zwlr = wayland.client.zwlr;
|
||||||
|
const pixman = @import("pixman");
|
||||||
|
|
||||||
const utils = @import("utils.zig");
|
const utils = @import("utils.zig");
|
||||||
|
const Buffer = @import("Buffer.zig");
|
||||||
const Context = @import("Context.zig");
|
const Context = @import("Context.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ pending_manage: PendingManage = .{},
|
||||||
/// State consumed in render() phase, reset at end of render().
|
/// State consumed in render() phase, reset at end of render().
|
||||||
pending_render: PendingRender = .{},
|
pending_render: PendingRender = .{},
|
||||||
|
|
||||||
/// Used to put Windows into a list in
|
/// Used to put Windows into a list in calculatePrimaryStackLayout()
|
||||||
/// WindowManager.calculatePrimaryStackLayout()
|
|
||||||
active_list_node: DoublyLinkedList.Node = .{},
|
active_list_node: DoublyLinkedList.Node = .{},
|
||||||
|
|
||||||
link: wl.list.Link,
|
link: wl.list.Link,
|
||||||
|
|
@ -141,10 +140,10 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event,
|
||||||
window.destroy();
|
window.destroy();
|
||||||
},
|
},
|
||||||
.dimensions => |ev| {
|
.dimensions => |ev| {
|
||||||
// The protocol requires these are strictly greater than zero.
|
// Protocol guarantees that width and height are strictly greater than zero
|
||||||
assert(ev.width > 0 and ev.height > 0);
|
assert(ev.width > 0 and ev.height > 0);
|
||||||
window.width = @intCast(ev.width);
|
window.pending_manage.width = @intCast(ev.width);
|
||||||
window.height = @intCast(ev.height);
|
window.pending_manage.height = @intCast(ev.height);
|
||||||
},
|
},
|
||||||
.dimensions_hint => {
|
.dimensions_hint => {
|
||||||
// TODO: Maybe could use this for floating windows
|
// TODO: Maybe could use this for floating windows
|
||||||
|
|
|
||||||
|
|
@ -179,10 +179,12 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
|
||||||
const output = Output.create(context, ev.id) catch @panic("Out of memory");
|
const output = Output.create(context, ev.id) catch @panic("Out of memory");
|
||||||
wm.outputs.append(output);
|
wm.outputs.append(output);
|
||||||
// If there was already a seat, but no outputs, set this new output as focused
|
// If there was already a seat, but no outputs, set this new output as focused
|
||||||
const seat = wm.seats.first() orelse return;
|
const first_seat = wm.seats.first();
|
||||||
|
if (first_seat) |seat| {
|
||||||
if (seat.focused_output == null and seat.pending_manage.output == null) {
|
if (seat.focused_output == null and seat.pending_manage.output == null) {
|
||||||
seat.pending_manage.output = .{ .output = output };
|
seat.pending_manage.output = .{ .output = output };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there are orphan windows, send them to the new output
|
// If there are orphan windows, send them to the new output
|
||||||
var it = wm.orphan_windows.iterator(.forward);
|
var it = wm.orphan_windows.iterator(.forward);
|
||||||
|
|
@ -210,7 +212,9 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv
|
||||||
wm.seats.append(seat);
|
wm.seats.append(seat);
|
||||||
|
|
||||||
// If there was already an output, but no seats, set the first output as focused
|
// If there was already an output, but no seats, set the first output as focused
|
||||||
seat.pending_manage.output = .{ .output = wm.outputs.first() orelse return };
|
if (wm.outputs.first()) |output| {
|
||||||
|
seat.pending_manage.output = .{ .output = output };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.window => |ev| {
|
.window => |ev| {
|
||||||
// TODO: Support multiple seats
|
// TODO: Support multiple seats
|
||||||
|
|
|
||||||
58
src/main.zig
58
src/main.zig
|
|
@ -7,10 +7,20 @@ const Globals = struct {
|
||||||
river_layer_shell_v1: ?*river.LayerShellV1 = null,
|
river_layer_shell_v1: ?*river.LayerShellV1 = null,
|
||||||
river_window_manager_v1: ?*river.WindowManagerV1 = null,
|
river_window_manager_v1: ?*river.WindowManagerV1 = null,
|
||||||
river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null,
|
river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null,
|
||||||
|
|
||||||
wl_compositor: ?*wl.Compositor = null,
|
wl_compositor: ?*wl.Compositor = null,
|
||||||
wl_output: ?*wl.Output = null,
|
|
||||||
wl_shm: ?*wl.Shm = null,
|
wl_shm: ?*wl.Shm = null,
|
||||||
|
wl_outputs: std.AutoHashMapUnmanaged(u32, *wl.Output) = .empty,
|
||||||
|
|
||||||
zwlr_layer_shell_v1: ?*zwlr.LayerShellV1 = null,
|
zwlr_layer_shell_v1: ?*zwlr.LayerShellV1 = null,
|
||||||
|
|
||||||
|
fn deinit(globals: *Globals) void {
|
||||||
|
var it = globals.wl_outputs.valueIterator();
|
||||||
|
while (it.next()) |output| {
|
||||||
|
output.*.release();
|
||||||
|
}
|
||||||
|
globals.wl_outputs.deinit(utils.allocator);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
|
@ -27,6 +37,7 @@ pub fn main() !void {
|
||||||
const wl_registry = try wl_display.getRegistry();
|
const wl_registry = try wl_display.getRegistry();
|
||||||
|
|
||||||
var globals: Globals = .{};
|
var globals: Globals = .{};
|
||||||
|
defer globals.deinit();
|
||||||
wl_registry.setListener(*Globals, registryListener, &globals);
|
wl_registry.setListener(*Globals, registryListener, &globals);
|
||||||
|
|
||||||
const errno = wl_display.roundtrip();
|
const errno = wl_display.roundtrip();
|
||||||
|
|
@ -34,12 +45,15 @@ pub fn main() !void {
|
||||||
fatal("Initial roundtrip failed: E{s}", .{@tagName(errno)});
|
fatal("Initial roundtrip failed: E{s}", .{@tagName(errno)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wl_compositor = globals.wl_compositor orelse utils.interfaceNotAdvertised(wl.Compositor);
|
||||||
|
const wl_shm = globals.wl_shm orelse utils.interfaceNotAdvertised(wl.Shm);
|
||||||
|
// We can theoretically start with zero wl_outputs; don't panic if it's empty.
|
||||||
|
const wl_outputs = &globals.wl_outputs;
|
||||||
|
|
||||||
const river_layer_shell_v1 = globals.river_layer_shell_v1 orelse utils.interfaceNotAdvertised(river.LayerShellV1);
|
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_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 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 zwlr_layer_shell_v1 = globals.zwlr_layer_shell_v1 orelse utils.interfaceNotAdvertised(zwlr.LayerShellV1);
|
||||||
|
|
||||||
const config = try Config.create();
|
const config = try Config.create();
|
||||||
|
|
@ -47,7 +61,7 @@ pub fn main() !void {
|
||||||
const context = try Context.create(.{
|
const context = try Context.create(.{
|
||||||
.wl_compositor = wl_compositor,
|
.wl_compositor = wl_compositor,
|
||||||
.wl_display = wl_display,
|
.wl_display = wl_display,
|
||||||
.wl_output = wl_output,
|
.wl_outputs = wl_outputs,
|
||||||
.wl_registry = wl_registry,
|
.wl_registry = wl_registry,
|
||||||
.wl_shm = wl_shm,
|
.wl_shm = wl_shm,
|
||||||
.river_layer_shell_v1 = river_layer_shell_v1,
|
.river_layer_shell_v1 = river_layer_shell_v1,
|
||||||
|
|
@ -73,20 +87,46 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *
|
||||||
if (mem.orderZ(u8, ev.interface, wl.Compositor.interface.name) == .eq) {
|
if (mem.orderZ(u8, ev.interface, wl.Compositor.interface.name) == .eq) {
|
||||||
if (ev.version < 4) utils.versionNotSupported(wl.Compositor, ev.version, 4);
|
if (ev.version < 4) utils.versionNotSupported(wl.Compositor, ev.version, 4);
|
||||||
globals.wl_compositor = registry.bind(ev.name, wl.Compositor, 4) catch |e| {
|
globals.wl_compositor = registry.bind(ev.name, wl.Compositor, 4) catch |e| {
|
||||||
fatal("Failed to bind to compositor: {any}", .{@errorName(e)});
|
fatal("Failed to bind to wl_compositor: {any}", .{@errorName(e)});
|
||||||
|
};
|
||||||
|
} else if (mem.orderZ(u8, ev.interface, wl.Output.interface.name) == .eq) {
|
||||||
|
if (ev.version < 4) utils.versionNotSupported(wl.Output, ev.version, 4);
|
||||||
|
|
||||||
|
const wl_output = registry.bind(ev.name, wl.Output, 4) catch |e| {
|
||||||
|
fatal("Failed to bind to wl_output: {any}", .{@errorName(e)});
|
||||||
|
};
|
||||||
|
|
||||||
|
// We can get multiple wl_outputs, so we have to try add them to our HashMap
|
||||||
|
// instead of just keeping the one
|
||||||
|
globals.wl_outputs.put(utils.allocator, ev.name, wl_output) catch |e| {
|
||||||
|
fatal("Failed to add wl_output to hashmap: {any}", .{@errorName(e)});
|
||||||
|
};
|
||||||
|
} else if (mem.orderZ(u8, ev.interface, wl.Shm.interface.name) == .eq) {
|
||||||
|
globals.wl_shm = registry.bind(ev.name, wl.Shm, 1) catch |e| {
|
||||||
|
fatal("Failed to bind to wl_shm: {any}", .{@errorName(e)});
|
||||||
};
|
};
|
||||||
} else if (mem.orderZ(u8, ev.interface, river.WindowManagerV1.interface.name) == .eq) {
|
} else if (mem.orderZ(u8, ev.interface, river.WindowManagerV1.interface.name) == .eq) {
|
||||||
globals.river_window_manager_v1 = registry.bind(ev.name, river.WindowManagerV1, 3) catch |e| {
|
globals.river_window_manager_v1 = registry.bind(ev.name, river.WindowManagerV1, 3) catch |e| {
|
||||||
fatal("Failed to bind to window_manager_v1: {any}", .{@errorName(e)});
|
fatal("Failed to bind to river_window_manager_v1: {any}", .{@errorName(e)});
|
||||||
};
|
};
|
||||||
} else if (mem.orderZ(u8, ev.interface, river.XkbBindingsV1.interface.name) == .eq) {
|
} else if (mem.orderZ(u8, ev.interface, river.XkbBindingsV1.interface.name) == .eq) {
|
||||||
globals.river_xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
globals.river_xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| {
|
||||||
fatal("Failed to bind to xkb_bindings_v1: {any}", .{@errorName(e)});
|
fatal("Failed to bind to river_xkb_bindings_v1: {any}", .{@errorName(e)});
|
||||||
|
};
|
||||||
|
} else if (mem.orderZ(u8, ev.interface, zwlr.LayerShellV1.interface.name) == .eq) {
|
||||||
|
if (ev.version < 3) utils.versionNotSupported(zwlr.LayerShellV1, ev.version, 3);
|
||||||
|
globals.zwlr_layer_shell_v1 = registry.bind(ev.name, zwlr.LayerShellV1, 3) catch |e| {
|
||||||
|
fatal("Failed to bind to zwlr_layer_shell_v1: {any}", .{@errorName(e)});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// We don't need .global_remove
|
// We don't need .global_remove
|
||||||
.global_remove => {},
|
.global_remove => |ev| {
|
||||||
|
// The only remove we care about is for wl_outputs
|
||||||
|
if (!globals.wl_outputs.remove(ev.name)) {
|
||||||
|
log.debug("Received a global_remove event for something other than a wl_output", .{});
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue