From 30231f1149c8fdf5b96d095044427f9351742ba0 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Sat, 24 Jan 2026 17:48:01 -0600 Subject: [PATCH] Refactor initialization and Context struct I tried to make it a little bit easier to follow and get rid of the need to call back to context.x.y.z (as much) [I hope] --- src/Context.zig | 66 +++++++++++++++++++++ src/Output.zig | 5 +- src/Seat.zig | 5 +- src/Window.zig | 5 +- src/WindowManager.zig | 66 ++++++++++++--------- src/XkbBindings.zig | 25 ++++---- src/main.zig | 131 +++++++++++++++++++----------------------- src/utils.zig | 2 + 8 files changed, 187 insertions(+), 118 deletions(-) create mode 100644 src/Context.zig diff --git a/src/Context.zig b/src/Context.zig new file mode 100644 index 0000000..8001b4e --- /dev/null +++ b/src/Context.zig @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 Ben Buhse +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +// + +/// Context to pass Wayland info around. +const Context = @This(); + +initialized: bool, + +// Wayland globals +wl_display: *wl.Display, +wl_registry: *wl.Registry, +wl_compositor: *wl.Compositor, + +// Wayland globals that we have structs for +wm: *WindowManager, +xkb_bindings: *XkbBindings, + +// WM Configuration +config: Config, + +pub fn create( + wl_display: *wl.Display, + wl_registry: *wl.Registry, + wl_compositor: *wl.Compositor, + river_window_manager_v1: *river.WindowManagerV1, + river_xkb_bindings_v1: *river.XkbBindingsV1, + config: Config, +) !*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, + }; + + return context; +} + +pub fn destroy(context: *Context) void { + context.xkb_bindings.destroy(); + context.wm.destroy(); + + utils.allocator.destroy(context); +} + +const std = @import("std"); + +const wayland = @import("wayland"); +const river = wayland.client.river; +const wl = wayland.client.wl; + +const utils = @import("utils.zig"); +const Config = @import("Config.zig"); +const WindowManager = @import("WindowManager.zig"); +const XkbBindings = @import("XkbBindings.zig"); + +const log = std.log.scoped(.Context); diff --git a/src/Output.zig b/src/Output.zig index 5b3e570..c508ca8 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -30,7 +30,7 @@ fn outputListener(river_output_v1: *river.OutputV1, event: river.OutputV1.Event, switch (event) { .removed => { river_output_v1.destroy(); - output.context.allocator.destroy(output); + utils.allocator.destroy(output); }, .wl_output => { // log.debug("initializing new river_output_v1 corresponding to wl_output: {d}", .{ev.name}); @@ -61,6 +61,7 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; -const Context = @import("main.zig").Context; +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); const log = std.log.scoped(.Output); diff --git a/src/Seat.zig b/src/Seat.zig index e5de30e..15e90f7 100644 --- a/src/Seat.zig +++ b/src/Seat.zig @@ -28,7 +28,7 @@ fn seatListener(river_seat_v1: *river.SeatV1, event: river.SeatV1.Event, seat: * switch (event) { .removed => { river_seat_v1.destroy(); - seat.context.allocator.destroy(seat); + utils.allocator.destroy(seat); }, .wl_seat => { // log.debug("initializing new river_seat_v1 corresponding to wl_seat: {d}", .{ev.name}); @@ -66,7 +66,8 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; -const Context = @import("main.zig").Context; +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); const Window = @import("Window.zig"); const log = std.log.scoped(.Seat); diff --git a/src/Window.zig b/src/Window.zig index 4124b12..2c4ce3a 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -66,7 +66,7 @@ fn windowListener(river_window_v1: *river.WindowV1, event: river.WindowV1.Event, } } window.link.remove(); - window.context.allocator.destroy(window); + utils.allocator.destroy(window); }, .dimensions => |ev| { // The protocol requires these are strictly greather than zero. @@ -172,6 +172,7 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; -const Context = @import("main.zig").Context; +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); const log = std.log.scoped(.Window); diff --git a/src/WindowManager.zig b/src/WindowManager.zig index 5a4116a..fe7b526 100644 --- a/src/WindowManager.zig +++ b/src/WindowManager.zig @@ -16,6 +16,31 @@ windows: wl.list.Head(Window, .link), window_count: u8 = 0, +pub fn create(context: *Context, window_manager_v1: *river.WindowManagerV1) !*WindowManager { + const wm = try utils.allocator.create(WindowManager); + errdefer wm.destroy(); + + wm.* = .{ + .context = context, + .window_manager_v1 = window_manager_v1, + .seats = undefined, // we will initialize these shortly + .outputs = undefined, + .windows = undefined, + }; + + wm.seats.init(); + wm.outputs.init(); + wm.windows.init(); + + wm.window_manager_v1.setListener(*WindowManager, windowManagerV1Listener, wm); + + return wm; +} + +pub fn destroy(wm: *WindowManager) void { + utils.allocator.destroy(wm); +} + /// Get the next window in the list, wrapping to first if at end pub fn getNextWindow(wm: *WindowManager, current: *Window) ?*Window { var it = wm.windows.iterator(.forward); @@ -40,23 +65,6 @@ pub fn getPrevWindow(wm: *WindowManager, current: *Window) ?*Window { return wm.windows.last(); } -pub fn init(wm: *WindowManager, context: *Context, window_manager_v1: *river.WindowManagerV1) void { - assert(wm == &context.wm); - wm.* = .{ - .context = context, - .window_manager_v1 = window_manager_v1, - .seats = undefined, // we will initialize this shortly - .outputs = undefined, - .windows = undefined, - }; - - wm.seats.init(); - wm.outputs.init(); - wm.windows.init(); - - wm.window_manager_v1.setListener(*WindowManager, windowManagerV1Listener, wm); -} - /// Calculate primary/stack layout positions for all windows. /// - Single window: fullscreen /// - Multiple windows: stack (45% left, vertically tiled), primary (55% right) @@ -137,12 +145,15 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv // mark it as cold. @branchHint(.cold); context.initialized = true; - context.xkb_bindings.addBinding(xkbcommon.Keysym.t, .{ .mod4 = true }, .{ .spawn = &.{"foot"} }); - context.xkb_bindings.addBinding(xkbcommon.Keysym.j, .{ .mod4 = true }, .focus_next); - context.xkb_bindings.addBinding(xkbcommon.Keysym.k, .{ .mod4 = true }, .focus_prev); - context.xkb_bindings.addBinding(xkbcommon.Keysym.f, .{ .mod4 = true }, .fullscreen); - context.xkb_bindings.addBinding(xkbcommon.Keysym.q, .{ .mod4 = true, .shift = true }, .close_window); - context.xkb_bindings.addBinding(xkbcommon.Keysym.e, .{ .mod4 = true, .shift = true }, .exit); + + const seat = wm.seats.first() orelse @panic("No river_seat_v1 found"); + const river_seat_v1 = seat.seat_v1; + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.t, .{ .mod4 = true }, .{ .spawn = &.{"foot"} }); + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.j, .{ .mod4 = true }, .focus_next); + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.k, .{ .mod4 = true }, .focus_prev); + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.f, .{ .mod4 = true }, .fullscreen); + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.q, .{ .mod4 = true, .shift = true }, .close_window); + context.xkb_bindings.addBinding(river_seat_v1, xkbcommon.Keysym.e, .{ .mod4 = true, .shift = true }, .exit); } { @@ -190,19 +201,19 @@ fn windowManagerV1Listener(window_manager_v1: *river.WindowManagerV1, event: riv }, .output => |ev| { log.debug("3", .{}); - var output = context.allocator.create(Output) catch @panic("out-of-memory; exiting."); + var output = utils.allocator.create(Output) catch @panic("out-of-memory; exiting."); output.init(context, ev.id); wm.outputs.append(output); }, .seat => |ev| { log.debug("4", .{}); - var seat = context.allocator.create(Seat) catch @panic("out-of-memory; exiting."); + var seat = utils.allocator.create(Seat) catch @panic("out-of-memory; exiting."); seat.init(context, ev.id); wm.seats.append(seat); }, .window => |ev| { log.debug("5", .{}); - var window = context.allocator.create(Window) catch @panic("out-of-memory; exiting."); + var window = utils.allocator.create(Window) catch @panic("out-of-memory; exiting."); window.init(context, ev.id); wm.windows.append(window); wm.window_count += 1; @@ -223,7 +234,8 @@ const river = wayland.client.river; const xkbcommon = @import("xkbcommon"); -const Context = @import("main.zig").Context; +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); const Output = @import("Output.zig"); const Seat = @import("Seat.zig"); const Window = @import("Window.zig"); diff --git a/src/XkbBindings.zig b/src/XkbBindings.zig index 2c04401..f7992d7 100644 --- a/src/XkbBindings.zig +++ b/src/XkbBindings.zig @@ -97,10 +97,10 @@ xkb_bindings_v1: *river.XkbBindingsV1, bindings: wl.list.Head(XkbBinding, .link), -xkb_bindings_seat_v1: ?*river.XkbBindingsSeatV1 = null, +pub fn create(context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) !*XkbBindings { + const xkb_bindings = try utils.allocator.create(XkbBindings); + errdefer xkb_bindings.destroy(); -pub fn init(xkb_bindings: *XkbBindings, context: *Context, xkb_bindings_v1: *river.XkbBindingsV1) void { - assert(xkb_bindings == &context.xkb_bindings); xkb_bindings.* = .{ .context = context, .xkb_bindings_v1 = xkb_bindings_v1, @@ -108,21 +108,21 @@ pub fn init(xkb_bindings: *XkbBindings, context: *Context, xkb_bindings_v1: *riv }; xkb_bindings.bindings.init(); + + return xkb_bindings; } -pub fn getSeat(xkb_bindings: *XkbBindings) *river.SeatV1 { - const seat = xkb_bindings.context.wm.seats.first() orelse @panic("No seat available"); - return seat.seat_v1; +pub fn destroy(xkb_bindings: *XkbBindings) void { + xkb_bindings.destroy(); } -pub fn addBinding(xkb_bindings: *XkbBindings, keysym: u32, modifiers: river.SeatV1.Modifiers, command: Command) void { - const seat_v1 = xkb_bindings.getSeat(); - const xkb_binding_v1 = xkb_bindings.xkb_bindings_v1.getXkbBinding(seat_v1, keysym, modifiers) catch |err| { - log.err("Failed to get xkb binding: {}", .{err}); +pub fn addBinding(xkb_bindings: *XkbBindings, river_seat_v1: *river.SeatV1, keysym: u32, modifiers: river.SeatV1.Modifiers, command: Command) void { + const xkb_binding_v1 = xkb_bindings.xkb_bindings_v1.getXkbBinding(river_seat_v1, keysym, modifiers) catch |err| { + log.err("Failed to get river xkb binding: {}", .{err}); return; }; - const xkb_binding = xkb_bindings.context.allocator.create(XkbBinding) catch @panic("out-of-memory"); + const xkb_binding = utils.allocator.create(XkbBinding) catch @panic("out-of-memory"); xkb_binding.init(xkb_binding_v1, command, xkb_bindings.context); xkb_bindings.bindings.append(xkb_binding); @@ -138,7 +138,8 @@ const river = wayland.client.river; const xkbcommon = @import("xkbcommon"); -const Context = @import("main.zig").Context; +const utils = @import("utils.zig"); +const Context = @import("Context.zig"); const Seat = @import("Seat.zig"); const log = std.log.scoped(.XkbBindings); diff --git a/src/main.zig b/src/main.zig index c789dd4..af3b460 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,95 +2,52 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -/// Context to pass some info around. -pub const Context = struct { - allocator: mem.Allocator, - - initialized: bool, - - display: *wl.Display, - registry: *wl.Registry, - - compositor: ?*wl.Compositor = null, - shm: ?*wl.Shm = null, - - wm: WindowManager, - xkb_bindings: XkbBindings, - - config: Config, - - fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, context: *Context) void { - switch (event) { - .global => |ev| { - if (mem.orderZ(u8, ev.interface, wl.Compositor.interface.name) == .eq) { - if (ev.version < 4) versionNotSupported(wl.Compositor, ev.version, 4); - context.compositor = registry.bind(ev.name, wl.Compositor, 4) catch |e| { - log.err("Failed to bind to compositor: {any}", .{@errorName(e)}); - std.posix.exit(1); - }; - } else if (mem.orderZ(u8, ev.interface, wl.Shm.interface.name) == .eq) { - context.shm = registry.bind(ev.name, wl.Shm, 1) catch |e| { - log.err("Failed to bind to shm: {any}", .{@errorName(e)}); - std.posix.exit(1); - }; - } else if (mem.orderZ(u8, ev.interface, river.WindowManagerV1.interface.name) == .eq) { - const window_manager_v1 = registry.bind(ev.name, river.WindowManagerV1, 3) catch |e| { - log.err("Failed to bind to window_manager_v1: {any}", .{@errorName(e)}); - std.posix.exit(1); - }; - context.wm.init(context, window_manager_v1); - } else if (mem.orderZ(u8, ev.interface, river.XkbBindingsV1.interface.name) == .eq) { - const xkb_bindings_v1 = registry.bind(ev.name, river.XkbBindingsV1, 2) catch |e| { - log.err("Failed to bind to xkb_bindings_v1: {any}", .{@errorName(e)}); - std.posix.exit(1); - }; - context.xkb_bindings.init(context, xkb_bindings_v1); - } - }, - // We don't need .global_remove - .global_remove => {}, - } - } +/// Wayland globals that we need to bind to +const Globals = struct { + wl_compositor: ?*wl.Compositor = null, + river_window_manager_v1: ?*river.WindowManagerV1 = null, + river_xkb_bindings_v1: ?*river.XkbBindingsV1 = null, }; -const usage: []const u8 = - \\usage: beansprout [options] - \\ - \\ -i, --image TODO TODO TODO TODO - \\ -h, --help TODO TODO TODO TODO - \\ -V, --version TODO TODO TODO TODO - \\ -; - pub fn main() !void { - const allocator = std.heap.c_allocator; + const wayland_display_var = try utils.allocator.dupeZ(u8, process.getEnvVarOwned(utils.allocator, "WAYLAND_DISPLAY") catch { + log.err("Error getting WAYLAND_DISPLAY environment variable. Exiing", .{}); + std.posix.exit(1); + }); + defer utils.allocator.free(wayland_display_var); const wl_display = wl.Display.connect(null) catch { log.err("Error connecting to Wayland server. Exiting", .{}); std.posix.exit(1); }; + defer wl_display.disconnect(); - const registry = try wl_display.getRegistry(); + const wl_registry = try wl_display.getRegistry(); - var context: Context = .{ - .allocator = allocator, - .initialized = false, - .display = wl_display, - .registry = registry, - .wm = undefined, - .xkb_bindings = undefined, - // Hardcoded config for now - .config = .{ .border_width = 2, .border_color_focused = utils.parseRgbaComptime("0x89b4fa"), .border_color_unfocused = utils.parseRgbaComptime("0x1e1e2e") }, - }; + var globals: Globals = .{}; + wl_registry.setListener(*Globals, registryListener, &globals); - registry.setListener(*Context, Context.registryListener, &context); - - const errno = context.display.roundtrip(); + const errno = wl_display.roundtrip(); if (errno != .SUCCESS) { log.err("Initial roundtrip failed: E{s}", .{@tagName(errno)}); std.posix.exit(1); } + const wl_compositor = globals.wl_compositor orelse interfaceNotAdvertised(wl.Compositor); + const river_window_manager_v1 = globals.river_window_manager_v1 orelse interfaceNotAdvertised(river.WindowManagerV1); + const river_xkb_bindings_v1 = globals.river_xkb_bindings_v1 orelse interfaceNotAdvertised(river.XkbBindingsV1); + + const context = try Context.create( + wl_display, + wl_registry, + wl_compositor, + river_window_manager_v1, + river_xkb_bindings_v1, + // Hardcoded config for now + .{ .border_width = 2, .border_color_focused = utils.parseRgbaComptime("0x89b4fa"), .border_color_unfocused = utils.parseRgbaComptime("0x1e1e2e") }, + ); + defer context.destroy(); + while (true) { if (wl_display.dispatch() != .SUCCESS) { log.err("wayland display dispatch failed", .{}); @@ -101,6 +58,32 @@ pub fn main() !void { log.info("Exiting beansprout", .{}); } +fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void { + switch (event) { + .global => |ev| { + if (mem.orderZ(u8, ev.interface, wl.Compositor.interface.name) == .eq) { + if (ev.version < 4) versionNotSupported(wl.Compositor, ev.version, 4); + globals.wl_compositor = registry.bind(ev.name, wl.Compositor, 4) catch |e| { + log.err("Failed to bind to compositor: {any}", .{@errorName(e)}); + std.posix.exit(1); + }; + } 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| { + log.err("Failed to bind to window_manager_v1: {any}", .{@errorName(e)}); + std.posix.exit(1); + }; + } 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| { + log.err("Failed to bind to xkb_bindings_v1: {any}", .{@errorName(e)}); + std.posix.exit(1); + }; + } + }, + // We don't need .global_remove + .global_remove => {}, + } +} + // TODO: Actually use this... fn interfaceNotAdvertised(comptime WaylandGlobal: type) noreturn { log.err("{s} not advertised. Exiting", .{WaylandGlobal.interface.name}); @@ -114,6 +97,7 @@ fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need_ver const std = @import("std"); const mem = std.mem; +const process = std.process; const wayland = @import("wayland"); const river = wayland.client.river; @@ -121,6 +105,7 @@ const wl = wayland.client.wl; const utils = @import("utils.zig"); const Config = @import("Config.zig"); +const Context = @import("Context.zig"); const WindowManager = @import("WindowManager.zig"); const XkbBindings = @import("XkbBindings.zig"); diff --git a/src/utils.zig b/src/utils.zig index 8b47218..48f1d1e 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +pub const allocator = std.heap.c_allocator; + pub const RiverColor = struct { red: u32, green: u32,