stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | Submodules | README | LICENSE

commit f2cfbc477f3e962d13118e8c43fe5eef68ef5c6c
parent 3e383f3dbd3662b267ad6157c66efd2f70776155
Author: Andrea Feletto <andrea@andreafeletto.com>
Date:   Thu, 19 May 2022 19:12:46 +0200

wayland: fix flush/dispatch

Diffstat:
Mbuild.zig | 4++--
Msrc/Buffer.zig | 2+-
Msrc/Loop.zig | 41+++++++++++++++++++++++++++++++----------
Msrc/Modules.zig | 2++
Msrc/Monitor.zig | 2--
Msrc/Wayland.zig | 132++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/main.zig | 9+++++----
Msrc/modules/Backlight.zig | 11+++++++----
Msrc/modules/Battery.zig | 9+++++++--
Msrc/modules/Pulse.zig | 18++++++++++--------
Msrc/utils.zig | 7+++++++
11 files changed, 126 insertions(+), 111 deletions(-)

diff --git a/build.zig b/build.zig @@ -20,8 +20,8 @@ pub fn build(b: *std.build.Builder) void { scanner.generate("wl_compositor", 4); scanner.generate("wl_subcompositor", 1); scanner.generate("wl_shm", 1); - scanner.generate("wl_output", 4); - scanner.generate("wl_seat", 7); + scanner.generate("wl_output", 3); + scanner.generate("wl_seat", 5); scanner.generate("zwlr_layer_shell_v1", 1); scanner.generate("zriver_status_manager_v1", 1); scanner.generate("zriver_control_v1", 1); diff --git a/src/Buffer.zig b/src/Buffer.zig @@ -20,7 +20,7 @@ pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { self.width = width; self.height = height; - const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0); + const fd = try os.memfd_create("levee-shm", os.linux.MFD_CLOEXEC); defer os.close(fd); const stride = width * 4; diff --git a/src/Loop.zig b/src/Loop.zig @@ -1,8 +1,10 @@ const std = @import("std"); +const log = std.log; const mem = std.mem; const os = std.os; const State = @import("main.zig").State; +const utils = @import("utils.zig"); const Loop = @This(); state: *State, @@ -14,22 +16,24 @@ pub const Event = struct { callbackIn: Callback, callbackOut: Callback, - pub const Callback = fn (*anyopaque) void; + pub const Action = enum { ok, terminate }; + pub const Callback = fn (*anyopaque) Action; - pub fn terminate(_: *anyopaque) void { - os.exit(0); + pub fn terminate(_: *anyopaque) Action { + return .terminate; } - pub fn noop(_: *anyopaque) void { - return; + pub fn noop(_: *anyopaque) Action { + return .ok; } }; pub fn init(state: *State) !Loop { - var mask = mem.zeroes(os.linux.sigset_t); + var mask = os.empty_sigset; os.linux.sigaddset(&mask, os.linux.SIG.INT); os.linux.sigaddset(&mask, os.linux.SIG.TERM); os.linux.sigaddset(&mask, os.linux.SIG.QUIT); + _ = os.linux.sigprocmask(os.linux.SIG.BLOCK, &mask, null); const sfd = os.linux.signalfd(-1, &mask, os.linux.SFD.NONBLOCK); @@ -38,6 +42,7 @@ pub fn init(state: *State) !Loop { pub fn run(self: *Loop) !void { const gpa = self.state.gpa; + const display = self.state.wayland.display; var events: std.MultiArrayList(Event) = .{}; defer events.deinit(gpa); @@ -55,8 +60,16 @@ pub fn run(self: *Loop) !void { const fds = events.items(.fd); while (true) { - self.state.wayland.flushAndPrepareRead(); - _ = try os.poll(fds, -1); + while (true) { + const ret = display.dispatchPending(); + _ = display.flush(); + if (ret == .SUCCESS) break; + } + + _ = os.poll(fds, -1) catch |err| { + log.err("poll failed: {s}", .{@errorName(err)}); + return; + }; for (fds) |fd, i| { if (fd.revents & os.POLL.HUP != 0) return; @@ -64,11 +77,19 @@ pub fn run(self: *Loop) !void { if (fd.revents & os.POLL.IN != 0) { const event = events.get(i); - event.callbackIn(event.data); + const action = event.callbackIn(event.data); + switch (action) { + .ok => {}, + .terminate => return, + } } if (fd.revents & os.POLL.OUT != 0) { const event = events.get(i); - event.callbackOut(event.data); + const action = event.callbackOut(event.data); + switch (action) { + .ok => {}, + .terminate => return, + } } } } diff --git a/src/Modules.zig b/src/Modules.zig @@ -1,5 +1,6 @@ const std = @import("std"); const ArrayList = std.ArrayList; +const log = std.log; const Event = @import("Loop.zig").Event; const State = @import("main.zig").State; @@ -50,4 +51,5 @@ pub fn deinit(self: *Modules) void { pub fn register(self: *Modules, comptime ModuleType: type) !void { const instance = try ModuleType.create(self.state); try self.modules.append(try instance.module()); + log.info("registered module: {s}", .{@typeName(ModuleType)}); } diff --git a/src/Monitor.zig b/src/Monitor.zig @@ -42,8 +42,6 @@ fn listener(_: *wl.Output, event: wl.Output.Event, monitor: *Monitor) void { }, .geometry => {}, .mode => {}, - .name => {}, - .description => {}, .done => { if (monitor.bar) |_| {} else { monitor.bar = Bar.create(monitor) catch return; diff --git a/src/Wayland.zig b/src/Wayland.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const log = std.log; const mem = std.mem; const meta = std.meta; const os = std.os; @@ -19,7 +20,6 @@ const Wayland = @This(); state: *State, display: *wl.Display, -registry: *wl.Registry, monitors: ArrayList(*Monitor), inputs: ArrayList(*Input), @@ -37,12 +37,13 @@ const Globals = struct { const GlobalsMask = utils.Mask(Globals); pub fn init(state: *State) !Wayland { - const display = try wl.Display.connect(null); + const display = wl.Display.connect(null) catch |err| { + utils.fatal("failed to connect to a wayland compositor: {s}", .{@errorName(err)}); + }; return Wayland{ .state = state, .display = display, - .registry = try display.getRegistry(), .monitors = ArrayList(*Monitor).init(state.gpa), .inputs = ArrayList(*Input).init(state.gpa), .globals = undefined, @@ -56,18 +57,27 @@ pub fn deinit(self: *Wayland) void { self.monitors.deinit(); self.inputs.deinit(); + + inline for (@typeInfo(Globals).Struct.fields) |field| { + @field(self.globals, field.name).destroy(); + } + self.display.disconnect(); } pub fn registerGlobals(self: *Wayland) !void { - self.registry.setListener(*State, registryListener, self.state); - if (self.display.roundtrip() != .SUCCESS) { - std.log.err("failed roundtrip for initialization of globals", .{}); - os.exit(1); + const registry = self.display.getRegistry() catch |err| { + utils.fatal("out of memory during initialization: {s}", .{@errorName(err)}); + }; + defer registry.destroy(); + + registry.setListener(*State, registryListener, self.state); + const errno = self.display.roundtrip(); + if (errno != .SUCCESS) { + utils.fatal("initial roundtrip failed", .{}); } for (self.globalsMask) |is_registered| if (!is_registered) { - std.log.err("missing global", .{}); - os.exit(1); + utils.fatal("global not advertised", .{}); }; } @@ -81,94 +91,58 @@ pub fn getEvent(self: *Wayland) !Event { .revents = undefined, }, .data = @ptrCast(*anyopaque, self), - .callbackIn = read, - .callbackOut = Event.noop, + .callbackIn = dispatch, + .callbackOut = flush, }; } -pub fn flushAndPrepareRead(self: *Wayland) void { - while (!self.display.prepareRead()) { - const errno = self.display.dispatchPending(); - if (errno != .SUCCESS) { - std.log.err("failed to dispatch pending wayland events", .{}); - os.exit(1); - } - } - - while (true) { - const errno = self.display.flush(); - switch (errno) { - .SUCCESS => return, - .PIPE => { - _ = self.display.readEvents(); - std.log.err("connection to wayland server unexpectedly terminated", .{}); - os.exit(1); - }, - .AGAIN => { - var wayland_out = [_]os.pollfd{.{ - .fd = self.display.getFd(), - .events = os.POLL.OUT, - .revents = undefined, - }}; - _ = os.poll(&wayland_out, -1) catch { - std.log.err("polling for wayland socket being writable failed", .{}); - os.exit(1); - }; - }, - else => { - std.log.err("failed to flush wayland requests", .{}); - os.exit(1); - }, - } +fn dispatch(self_opaque: *anyopaque) Event.Action { + const self = utils.cast(Wayland)(self_opaque); + const errno = self.display.dispatch(); + switch (errno) { + .SUCCESS => return .ok, + else => return .terminate, } } -fn read(self_opaque: *anyopaque) void { +fn flush(self_opaque: *anyopaque) Event.Action { const self = utils.cast(Wayland)(self_opaque); - const errno = self.display.readEvents(); - if (errno != .SUCCESS) { - std.log.err("failed to read wayland events", .{}); - os.exit(1); + const errno = self.display.flush(); + switch (errno) { + .SUCCESS => return .ok, + else => return .terminate, } } -fn registryListener( - registry: *wl.Registry, - event: wl.Registry.Event, - state: *State, -) void { +fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *State) void { const self = &state.wayland; - switch (event) { .global => |g| { - self.bindGlobal(registry, g.interface, g.name) catch return; + self.bindGlobal(registry, g.name, g.interface, g.version) catch |err| switch (err) { + error.OutOfMemory => { + log.err("out of memory", .{}); + return; + }, + }; }, - .global_remove => |data| { - for (self.monitors.items) |monitor, i| { - if (monitor.globalName == data.name) { - monitor.destroy(); - _ = self.monitors.swapRemove(i); - break; - } - } - for (self.inputs.items) |input, i| { - if (input.globalName == data.name) { - input.destroy(); - _ = self.inputs.swapRemove(i); - break; - } - } + .global_remove => |g| { + for (self.monitors.items) |monitor, i| if (monitor.globalName == g.name) { + monitor.destroy(); + _ = self.monitors.swapRemove(i); + break; + }; + for (self.inputs.items) |input, i| if (input.globalName == g.name) { + input.destroy(); + _ = self.inputs.swapRemove(i); + break; + }; }, } } -fn bindGlobal( - self: *Wayland, - registry: *wl.Registry, - iface: [*:0]const u8, - name: u32, -) !void { +fn bindGlobal(self: *Wayland, registry: *wl.Registry, name: u32, iface: [*:0]const u8, version: u32) !void { if (strcmp(iface, wl.Compositor.getInterface().name) == 0) { + if (version < 4) utils.fatal("wl_compositor version 4 is required", .{}); const global = try registry.bind(name, wl.Compositor, 4); self.setGlobal(global); } else if (strcmp(iface, wl.Subcompositor.getInterface().name) == 0) { @@ -187,9 +161,11 @@ fn bindGlobal( const global = try registry.bind(name, zriver.ControlV1, 1); self.setGlobal(global); } else if (strcmp(iface, wl.Output.getInterface().name) == 0) { + if (version < 3) utils.fatal("wl_output version 3 is required", .{}); const monitor = try Monitor.create(self.state, registry, name); try self.monitors.append(monitor); } else if (strcmp(iface, wl.Seat.getInterface().name) == 0) { + if (version < 5) utils.fatal("wl_seat version 5 is required", .{}); const input = try Input.create(self.state, registry, name); try self.inputs.append(input); } diff --git a/src/main.zig b/src/main.zig @@ -1,6 +1,7 @@ const std = @import("std"); const heap = std.heap; const io = std.io; +const log = std.log; const mem = std.mem; const os = std.os; @@ -28,7 +29,7 @@ pub fn main() anyerror!void { // cli arguments const params = comptime [_]clap.Param(clap.Help){ - try clap.parseParam("-h, --help Display this help and exit."), + try clap.parseParam("-h, --help Display this help and exit."), try clap.parseParam("-m, --module <str>... Add module."), }; var args = try clap.parse(clap.Help, &params, .{}); @@ -56,13 +57,13 @@ pub fn main() anyerror!void { } else if (mem.eql(u8, module_name, "pulse")) { try state.modules.register(Modules.Pulse); } else { - std.log.err("unknown module: {s}", .{module_name}); - os.exit(1); + log.err("unknown module: {s}", .{module_name}); + return clap.help(io.getStdErr().writer(), &params); } } if (state.modules.modules.items.len == 0) { - std.log.err("having no module is currently not supported", .{}); + log.err("having no modules is currently not supported", .{}); return clap.help(io.getStdErr().writer(), &params); } diff --git a/src/modules/Backlight.zig b/src/modules/Backlight.zig @@ -1,5 +1,6 @@ const std = @import("std"); const fmt = std.fmt; +const log = std.log; const mem = std.mem; const os = std.os; @@ -68,13 +69,14 @@ fn getEvent(self_opaque: *anyopaque) !Event { }; } -fn callbackIn(self_opaque: *anyopaque) void { +fn callbackIn(self_opaque: *anyopaque) Event.Action { const self = utils.cast(Backlight)(self_opaque); - _ = self.monitor.receiveDevice() catch { - std.log.err("failed to receive udev device", .{}); - os.exit(1); + _ = self.monitor.receiveDevice() catch |err| { + log.err("failed to receive udev device: {s}", .{@errorName(err)}); + return .terminate; }; + for (self.state.wayland.monitors.items) |monitor| { if (monitor.bar) |bar| { if (bar.configured) { @@ -84,6 +86,7 @@ fn callbackIn(self_opaque: *anyopaque) void { } } } + return .ok; } fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void { diff --git a/src/modules/Battery.zig b/src/modules/Battery.zig @@ -1,5 +1,6 @@ const std = @import("std"); const fmt = std.fmt; +const log = std.log; const mem = std.mem; const os = std.os; @@ -107,11 +108,14 @@ pub fn destroy(self_opaque: *anyopaque) void { self.state.gpa.destroy(self); } -fn callbackIn(self_opaque: *anyopaque) void { +fn callbackIn(self_opaque: *anyopaque) Event.Action { const self = utils.cast(Battery)(self_opaque); var expirations = mem.zeroes([8]u8); - _ = os.read(self.timerFd, &expirations) catch return; + _ = os.read(self.timerFd, &expirations) catch |err| { + log.err("failed to read timer: {s}", .{@errorName(err)}); + return .terminate; + }; for (self.state.wayland.monitors.items) |monitor| { if (monitor.bar) |bar| { @@ -124,6 +128,7 @@ fn callbackIn(self_opaque: *anyopaque) void { } } } + return .ok; } fn updateDevices( diff --git a/src/modules/Pulse.zig b/src/modules/Pulse.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const log = std.log; const mem = std.mem; const os = std.os; @@ -67,13 +68,13 @@ fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void { } } -fn callbackIn(self_opaque: *anyopaque) void { +fn callbackIn(self_opaque: *anyopaque) Event.Action { const self = utils.cast(Pulse)(self_opaque); var data = mem.zeroes([8]u8); - _ = os.read(self.fd, &data) catch { - std.log.err("read failed", .{}); - os.exit(1); + _ = os.read(self.fd, &data) catch |err| { + log.err("pulse: failed to read: {s}", .{@errorName(err)}); + return .terminate; }; for (self.state.wayland.monitors.items) |monitor| { @@ -87,6 +88,7 @@ fn callbackIn(self_opaque: *anyopaque) void { } } } + return .ok; } fn destroy(self_opaque: *anyopaque) void { @@ -153,10 +155,10 @@ export fn contextStateCallback( _ = pulse.pa_context_subscribe(ctx, mask, null, null); }, pulse.PA_CONTEXT_TERMINATED, pulse.PA_CONTEXT_FAILED => { - std.log.info("pulse: restarting", .{}); + log.info("pulse: restarting", .{}); self.deinitPulse(); self.initPulse() catch return; - std.log.info("pulse: restarted", .{}); + log.info("pulse: restarted", .{}); }, else => {}, } @@ -171,7 +173,7 @@ export fn serverInfoCallback( self.sink_name = mem.span(info.?.default_sink_name); self.sink_is_running = true; - std.log.info("pulse: sink set to {s}", .{self.sink_name}); + log.info("pulse: sink set to {s}", .{self.sink_name}); _ = pulse.pa_context_get_sink_info_list(ctx, sinkInfoCallback, self_opaque); } @@ -214,7 +216,7 @@ export fn sinkInfoCallback( if (!self.sink_is_running and is_running) { self.sink_name = sink_name; self.sink_is_running = true; - std.log.info("pulse: sink set to {s}", .{sink_name}); + log.info("pulse: sink set to {s}", .{sink_name}); } self.volume = volume: { diff --git a/src/utils.zig b/src/utils.zig @@ -1,8 +1,15 @@ const std = @import("std"); +const log = std.log; const mem = std.mem; const meta = std.meta; +const os = std.os; const unicode = std.unicode; +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { + log.err(format, args); + os.exit(1); +} + pub fn cast(comptime to: type) fn (*anyopaque) *to { return (struct { pub fn cast(module: *anyopaque) *to {