Split main() up by adding parseArgs() and run()

parseArgs() contains all of the argument parsing logic in a single fn.

run() handles the event loop. To work with the bar, I had to re-write
the loop to use polling similar to the loop in `beanclock` instead of
just `while (true) dispatch`.
This commit is contained in:
Ben Buhse 2026-02-13 11:10:42 -06:00
commit 5501ccbe26
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
3 changed files with 125 additions and 48 deletions

View file

@ -114,7 +114,6 @@ pub fn layerSurfaceListener(
bar.configured = true;
log.debug("bwbuhse before render", .{});
bar.render() catch |err| {
log.err("Bar render failed: {}", .{err});
};
@ -125,7 +124,9 @@ pub fn layerSurfaceListener(
}
}
fn render(bar: *Bar) !void {
// TODO: Configure number of visible tags
/// Renders the bar and its components
pub fn render(bar: *Bar) !void {
const buffer = try bar.context.buffer_pool.nextBuffer(bar.context.wl_shm, bar.width, bar.height);
// Fill with a solid color (e.g., dark background)
@ -138,7 +139,6 @@ fn render(bar: *Bar) !void {
wl_surface.attach(buffer.wl_buffer, 0, 0);
wl_surface.damageBuffer(0, 0, bar.width, bar.height);
wl_surface.commit();
log.debug("bwbuhse end of render", .{});
}
// Borrowed and modified from https://git.sr.ht/~novakane/zig-fcft-example

View file

@ -107,7 +107,6 @@ pub fn destroy(libinput_device: *LibinputDevice) void {
fn riverLibinputDeviceV1Listener(river_libinput_device_v1: *river.LibinputDeviceV1, event: river.LibinputDeviceV1.Event, libinput_device: *LibinputDevice) void {
assert(libinput_device.river_libinput_device_v1 == river_libinput_device_v1);
const im = libinput_device.context.im;
log.debug("bwbuhse: {s} for {d}", .{ @tagName(event), river_libinput_device_v1.getId() });
switch (event) {
.removed => {
river_libinput_device_v1.destroy();

View file

@ -36,48 +36,8 @@ const usage: []const u8 =
\\
;
// TODO: I'd like to clean this function up a bit and move some bits into helpers
pub fn main() !void {
const result = flags.parser([*:0]const u8, &.{
.{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean },
.{ .name = "log-level", .kind = .arg },
}).parse(std.os.argv[1..]) catch {
try stderr.writeAll(usage);
try stderr.flush();
posix.exit(1);
};
if (result.flags.h) {
try stdout.writeAll(usage);
try stdout.flush();
posix.exit(0);
}
if (result.args.len != 0) {
log.err("unknown option '{s}'", .{result.args[0]});
try stderr.writeAll(usage);
try stderr.flush();
posix.exit(1);
}
if (result.flags.version) {
try stdout.writeAll(build_options.version ++ "\n");
try stdout.flush();
posix.exit(0);
}
if (result.flags.@"log-level") |level| {
if (mem.eql(u8, level, "error")) {
runtime_log_level = .err;
} else if (mem.eql(u8, level, "warning")) {
runtime_log_level = .warn;
} else if (mem.eql(u8, level, "info")) {
runtime_log_level = .info;
} else if (mem.eql(u8, level, "debug")) {
runtime_log_level = .debug;
} else {
log.err("invalid log level '{s}'", .{level});
posix.exit(1);
}
}
parseArgs();
// Initialize fcft
const fcft_log_level: fcft.LogClass = switch (runtime_log_level) {
@ -141,13 +101,130 @@ pub fn main() !void {
});
defer context.destroy();
try run(wl_display, context);
}
/// Function to handle the main event loop
///
/// Since we've added a bar with a clock,we need
fn run(wl_display: *wl.Display, context: *Context) !void {
var mask = posix.sigemptyset();
posix.sigaddset(&mask, posix.SIG.INT);
posix.sigaddset(&mask, posix.SIG.QUIT);
posix.sigprocmask(posix.SIG.BLOCK, &mask, null);
const sig_fd = try posix.signalfd(-1, &mask, 0);
const poll_wayland = 0;
const poll_sig = 1;
var pollfds: [2]posix.pollfd = undefined;
pollfds[poll_wayland] = .{
.fd = wl_display.getFd(),
.events = posix.POLL.IN,
.revents = 0,
};
pollfds[poll_sig] = .{
.fd = sig_fd,
.events = posix.POLL.IN,
.revents = 0,
};
while (true) {
if (wl_display.dispatch() != .SUCCESS) {
fatal("wayland display dispatch failed", .{});
const errno = wl_display.flush();
if (errno != .SUCCESS) {
fatal("wl_display flush failed: E{s}", .{@tagName(errno)});
}
// Get the number of milliseconds to the top of the next minute
const time = std.time.timestamp();
if (time < 0) {
log.err("Got a negative time ({d})", .{time});
return error.InvalidTime;
}
const timeout: i32 = @intCast((@divTrunc(time, 60) * 60 + 60 - time) * 1000);
const poll_rc = posix.poll(&pollfds, timeout) catch |err| {
fatal("Failed to poll {s}", .{@errorName(err)});
};
if (poll_rc == 0) {
// If poll returns 0, it timed out, meaning we hit the top of the minute
// and need to update the clock.
var it = context.wm.outputs.iterator(.forward);
while (it.next()) |output| {
if (output.bar) |*bar| {
bar.render() catch |err| {
log.err("Bar timer render failed: {}", .{err});
};
}
}
}
// Handle fds that became ready
if (pollfds[poll_wayland].revents & posix.POLL.HUP != 0) {
log.info("Disconnected by compositor", .{});
break;
}
if (pollfds[poll_wayland].revents & posix.POLL.IN != 0) {
if (wl_display.dispatch() != .SUCCESS) {
fatal("Wayland display dispatch failed", .{});
}
}
if (pollfds[poll_sig].revents & posix.POLL.HUP != 0) {
fatal("Signal fd hung up", .{});
}
if (pollfds[poll_sig].revents & posix.POLL.IN != 0) {
log.info("Exiting beansprout", .{});
break;
}
}
}
log.info("Exiting beansprout", .{});
fn parseArgs() void {
const result = flags.parser([*:0]const u8, &.{
.{ .name = "h", .kind = .boolean },
.{ .name = "version", .kind = .boolean },
.{ .name = "log-level", .kind = .arg },
}).parse(os.argv[1..]) catch {
stderr.writeAll(usage) catch {};
stderr.flush() catch {};
posix.exit(1);
};
if (result.flags.h) {
stdout.writeAll(usage) catch {};
stdout.flush() catch {};
posix.exit(0);
}
if (result.args.len != 0) {
log.err("unknown option '{s}'", .{result.args[0]});
stderr.writeAll(usage) catch {};
stderr.flush() catch {};
posix.exit(1);
}
if (result.flags.version) {
stdout.writeAll(build_options.version ++ "\n") catch {};
stdout.flush() catch {};
posix.exit(0);
}
if (result.flags.@"log-level") |level| {
if (mem.eql(u8, level, "error")) {
runtime_log_level = .err;
} else if (mem.eql(u8, level, "warning")) {
runtime_log_level = .warn;
} else if (mem.eql(u8, level, "info")) {
runtime_log_level = .info;
} else if (mem.eql(u8, level, "debug")) {
runtime_log_level = .debug;
} else {
log.err("invalid log level '{s}'", .{level});
posix.exit(1);
}
}
}
fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
@ -250,6 +327,7 @@ const std = @import("std");
const fatal = std.process.fatal;
const fs = std.fs;
const mem = std.mem;
const os = std.os;
const posix = std.posix;
const process = std.process;