~andreafeletto/levee

485b6cb9f94ffd7c7a057b3a7fc0bbb9460b2932 — Andrea Feletto a month ago 4934bf6
make module creation dynamic
8 files changed, 196 insertions(+), 154 deletions(-)

M src/Loop.zig
A src/Modules.zig
M src/main.zig
D src/modules.zig
M src/modules/Alsa.zig
M src/modules/Backlight.zig
M src/modules/Battery.zig
M src/render.zig
M src/Loop.zig => src/Loop.zig +1 -1
@@ 50,7 50,7 @@ pub fn run(self: *Loop) !void {
        .callbackOut = Event.noop,
    });
    try events.append(gpa, try self.state.wayland.getEvent());
    for (self.state.modules.items) |*module| {
    for (self.state.modules.modules.items) |*module| {
        try events.append(gpa, try module.getEvent());
    }


A src/Modules.zig => src/Modules.zig +53 -0
@@ 0,0 1,53 @@
const std = @import("std");
const ArrayList = std.ArrayList;

const Event = @import("Loop.zig").Event;
const State = @import("main.zig").State;
const Modules = @This();

state: *State,
modules: ArrayList(Module),

pub const Alsa = @import("modules/Alsa.zig");
pub const Backlight = @import("modules/Backlight.zig");
pub const Battery = @import("modules/Battery.zig");

pub const Module = struct {
    impl: *anyopaque,
    funcs: struct {
        getEvent: fn (*anyopaque) anyerror!Event,
        print: fn (*anyopaque, StringWriter) anyerror!void,
        destroy: fn (*anyopaque) void,
    },

    pub const StringWriter = std.ArrayList(u8).Writer;

    pub fn getEvent(self: *Module) !Event {
        return self.funcs.getEvent(self.impl);
    }

    pub fn print(self: *Module, writer: StringWriter) !void {
        return self.funcs.print(self.impl, writer);
    }

    pub fn destroyInstance(self: *Module) void {
        return self.funcs.destroy(self.impl);
    }
};

pub fn init(state: *State) Modules {
    return Modules{
        .state = state,
        .modules = ArrayList(Module).init(state.gpa),
    };
}

pub fn deinit(self: *Modules) void {
    for (self.modules.items) |*module| module.destroyInstance();
    self.modules.deinit();
}

pub fn register(self: *Modules, comptime ModuleType: type) !void {
    const instance = try ModuleType.create(self.state);
    try self.modules.append(try instance.module());
}

M src/main.zig => src/main.zig +7 -20
@@ 6,19 6,15 @@ const fcft = @import("fcft");

const Config = @import("Config.zig");
const Loop = @import("Loop.zig");
const modules = @import("modules.zig");
const Modules = @import("Modules.zig");
const Wayland = @import("wayland.zig").Wayland;

pub const State = struct {
    gpa: mem.Allocator,
    config: Config,
    wayland: Wayland,
    modules: Modules,
    loop: Loop,

    alsa: modules.Alsa,
    backlight: modules.Backlight,
    battery: modules.Battery,
    modules: std.ArrayList(modules.Module),
};

pub fn main() anyerror!void {


@@ 33,23 29,14 @@ pub fn main() anyerror!void {
    state.config = try Config.init();
    state.wayland = try Wayland.init(&state);
    defer state.wayland.deinit();
    state.modules = Modules.init(&state);
    defer state.modules.deinit();
    state.loop = try Loop.init(&state);

    // modules
    state.modules = std.ArrayList(modules.Module).init(state.gpa);
    defer state.modules.deinit();

    state.alsa = try modules.Alsa.init(&state);
    state.backlight = try modules.Backlight.init(&state);
    defer state.backlight.deinit();
    state.battery = try modules.Battery.init(&state);
    defer state.battery.deinit();

    try state.modules.appendSlice(&.{
        try state.backlight.module(),
        state.battery.module(),
        state.alsa.module(),
    });
    try state.modules.register(Modules.Alsa);
    try state.modules.register(Modules.Backlight);
    try state.modules.register(Modules.Battery);

    // event loop
    try state.wayland.registerGlobals();

D src/modules.zig => src/modules.zig +0 -23
@@ 1,23 0,0 @@
const std = @import("std");

const Event = @import("Loop.zig").Event;

pub const Alsa = @import("modules/Alsa.zig");
pub const Backlight = @import("modules/Backlight.zig");
pub const Battery = @import("modules/Battery.zig");

pub const Module = struct {
    impl: *anyopaque,
    eventFn: fn (*anyopaque) anyerror!Event,
    printFn: fn (*anyopaque, StringWriter) anyerror!void,

    pub const StringWriter = std.ArrayList(u8).Writer;

    pub fn getEvent(self: *Module) !Event {
        return self.eventFn(self.impl);
    }

    pub fn print(self: *Module, writer: StringWriter) !void {
        return self.printFn(self.impl, writer);
    }
};

M src/modules/Alsa.zig => src/modules/Alsa.zig +45 -22
@@ 6,7 6,7 @@ const os = std.os;

const alsa = @cImport(@cInclude("alsa/asoundlib.h"));

const Module = @import("../modules.zig").Module;
const Module = @import("../Modules.zig").Module;
const Event = @import("../Loop.zig").Event;
const render = @import("../render.zig");
const State = @import("../main.zig").State;


@@ 14,20 14,44 @@ const utils = @import("../utils.zig");
const Alsa = @This();

state: *State,
context: *alsa.snd_ctl_t,
devices: DeviceList,

pub fn init(state: *State) !Alsa {
    return Alsa{
const Device = struct {
    ctl: *alsa.snd_ctl_t,
    name: []const u8,
};

const DeviceList = std.ArrayList(Device);

pub fn create(state: *State) !*Alsa {
    const self = try state.gpa.create(Alsa);
    self.* = .{
        .state = state,
        .context = try getAlsaCtl(state.gpa),
        .devices = DeviceList.init(state.gpa),
    };

    var card: i32 = -1;
    while(alsa.snd_card_next(&card) >= 0 and card >= 0) {
        const name = try fmt.allocPrintZ(state.gpa, "hw:{d}", .{ card });

        var ctl: ?*alsa.snd_ctl_t = null;
        _ = alsa.snd_ctl_open(&ctl, name.ptr, alsa.SND_CTL_READONLY);
        _ = alsa.snd_ctl_subscribe_events(ctl, 1);

        try self.devices.append(.{ .ctl = ctl.?, .name = name });
    }

    return self;
}

pub fn module(self: *Alsa) Module {
    return .{
pub fn module(self: *Alsa) !Module {
    return Module{
        .impl = @ptrCast(*anyopaque, self),
        .eventFn = getEvent,
        .printFn = print,
        .funcs = .{
            .getEvent = getEvent,
            .print = print,
            .destroy = destroy,
        },
    };
}



@@ 35,7 59,8 @@ fn getEvent(self_opaque: *anyopaque) !Event {
    const self = utils.cast(Alsa)(self_opaque);

    var fd = mem.zeroes(alsa.pollfd);
    _ = alsa.snd_ctl_poll_descriptors(self.context, &fd, 1);
    const device = &self.devices.items[0];
    _ = alsa.snd_ctl_poll_descriptors(device.ctl, &fd, 1);

    return Event{
        .fd = @bitCast(os.pollfd, fd),


@@ 61,7 86,6 @@ fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void {
    alsa.snd_mixer_selem_id_set_index(sid, 0);
    alsa.snd_mixer_selem_id_set_name(sid, "Master");
    const elem = alsa.snd_mixer_find_selem(handle, sid);
    _ = elem;

    var unmuted: i32 = 0;
    _ = alsa.snd_mixer_selem_get_playback_switch(


@@ 98,7 122,9 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    var event: ?*alsa.snd_ctl_event_t = null;
    _ = alsa.snd_ctl_event_malloc(&event);
    defer alsa.snd_ctl_event_free(event);
    _ = alsa.snd_ctl_read(self.context, event);

    const device = &self.devices.items[0];
    _ = alsa.snd_ctl_read(device.ctl, event);

    for (self.state.wayland.monitors.items) |monitor| {
        if (monitor.bar) |bar| {


@@ 113,15 139,12 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    }
}

fn getAlsaCtl(gpa: mem.Allocator) !*alsa.snd_ctl_t {
    var card: i32 = -1;
    _ = alsa.snd_card_next(&card);
    const name = try fmt.allocPrintZ(gpa, "hw:{d}", .{ card });
    defer gpa.free(name);

    var ctl: ?*alsa.snd_ctl_t = null;
    _ = alsa.snd_ctl_open(&ctl, name.ptr, alsa.SND_CTL_READONLY);
    _ = alsa.snd_ctl_subscribe_events(ctl, 1);
fn destroy(self_opaque: *anyopaque) void {
    const self = utils.cast(Alsa)(self_opaque);

    return ctl.?;
    for (self.devices.items) |*device| {
        self.state.gpa.free(device.name);
    }
    self.devices.deinit();
    self.state.gpa.destroy(self);
}

M src/modules/Backlight.zig => src/modules/Backlight.zig +31 -32
@@ 5,7 5,7 @@ const os = std.os;

const udev = @import("udev");

const Module = @import("../modules.zig").Module;
const Module = @import("../Modules.zig").Module;
const Event = @import("../Loop.zig").Event;
const render = @import("../render.zig");
const State = @import("../main.zig").State;


@@ 21,51 21,39 @@ const Device = struct {
    name: []const u8,
    value: u64,
    max: u64,

    pub fn deinit(self: *Device, gpa: mem.Allocator) void {
        gpa.free(self.name);
    }
};

const DeviceList = std.ArrayList(Device);

pub fn init(state: *State) !Backlight {
    const context = try udev.Udev.new();

    const monitor = try udev.Monitor.newFromNetlink(context, "udev");
    try monitor.filterAddMatchSubsystemDevType("backlight", null);
    try monitor.filterAddMatchSubsystemDevType("power_supply", null);
    try monitor.enableReceiving();
pub fn create(state: *State) !*Backlight {
    const self = try state.gpa.create(Backlight);
    self.state = state;
    self.context = try udev.Udev.new();

    var devices = DeviceList.init(state.gpa);
    try updateDevices(state.gpa, context, &devices);
    if (devices.items.len == 0) return error.NoDevicesFound;
    self.monitor = try udev.Monitor.newFromNetlink(self.context, "udev");
    try self.monitor.filterAddMatchSubsystemDevType("backlight", null);
    try self.monitor.filterAddMatchSubsystemDevType("power_supply", null);
    try self.monitor.enableReceiving();

    return Backlight{
        .state = state,
        .context = context,
        .monitor = monitor,
        .devices = devices,
    };
}
    self.devices = DeviceList.init(state.gpa);
    try updateDevices(state.gpa, self.context, &self.devices);
    if (self.devices.items.len == 0) return error.NoDevicesFound;

pub fn deinit(self: *Backlight) void {
    _ = self.context.unref();
    for (self.devices.items) |*device| {
        device.deinit(self.state.gpa);
    }
    self.devices.deinit();
    return self;
}

pub fn module(self: *Backlight) !Module {
    return Module{
        .impl = @ptrCast(*anyopaque, self),
        .eventFn = getEvent,
        .printFn = print,
        .funcs = .{
            .getEvent = getEvent,
            .print = print,
            .destroy = destroy,
        },
    };
}

pub fn getEvent(self_opaque: *anyopaque) !Event {
fn getEvent(self_opaque: *anyopaque) !Event {
    const self = utils.cast(Backlight)(self_opaque);

    return Event{


@@ 95,7 83,7 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    }
}

pub fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void {
fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void {
    const self = utils.cast(Backlight)(self_opaque);

    try updateDevices(self.state.gpa, self.context, &self.devices);


@@ 148,3 136,14 @@ fn updateOrAppend(
    device.value = try fmt.parseInt(u64, value, 10);
    device.max = try fmt.parseInt(u64, max, 10);
}

fn destroy(self_opaque: *anyopaque) void {
    const self = utils.cast(Backlight)(self_opaque);

    _ = self.context.unref();
    for (self.devices.items) |*device| {
        self.state.gpa.free(device.name);
    }
    self.devices.deinit();
    self.state.gpa.destroy(self);
}

M src/modules/Battery.zig => src/modules/Battery.zig +58 -55
@@ 5,7 5,7 @@ const os = std.os;

const udev = @import("udev");

const Module = @import("../modules.zig").Module;
const Module = @import("../Modules.zig").Module;
const Event = @import("../Loop.zig").Event;
const render = @import("../render.zig");
const State = @import("../main.zig").State;


@@ 21,53 21,44 @@ const Device = struct {
    name: []const u8,
    status: []const u8,
    capacity: u8,

    pub fn deinit(self: *Device, gpa: mem.Allocator) void {
        gpa.free(self.name);
        gpa.free(self.status);
    }
};

const DeviceList = std.ArrayList(Device);

pub fn init(state: *State) !Battery {
    const tfd = os.linux.timerfd_create(
        os.CLOCK.MONOTONIC,
        os.linux.TFD.CLOEXEC,
    );
    const interval: os.linux.itimerspec = .{
        .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 },
        .it_value = .{ .tv_sec = 10, .tv_nsec = 0 },
pub fn create(state: *State) !*Battery {
    const self = try state.gpa.create(Battery);
    self.state = state;

    self.timerFd = tfd: {
        const fd = os.linux.timerfd_create(
            os.CLOCK.MONOTONIC,
            os.linux.TFD.CLOEXEC,
        );
        const interval: os.linux.itimerspec = .{
            .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 },
            .it_value = .{ .tv_sec = 10, .tv_nsec = 0 },
        };
        _ = os.linux.timerfd_settime(@intCast(i32, fd), 0, &interval, null);
        break :tfd @intCast(os.fd_t, fd);
    };
    _ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null);

    const context = try udev.Udev.new();
    self.context = try udev.Udev.new();

    var devices = DeviceList.init(state.gpa);
    try updateDevices(state.gpa, context, &devices);
    if (devices.items.len == 0) return error.NoDevicesFound;
    self.devices = DeviceList.init(state.gpa);
    try updateDevices(state.gpa, self.context, &self.devices);
    if (self.devices.items.len == 0) return error.NoDevicesFound;

    return Battery{
        .state = state,
        .context = context,
        .timerFd = @intCast(os.fd_t, tfd),
        .devices = devices,
    };
}

pub fn deinit(self: *Battery) void {
    _ = self.context.unref();
    for (self.devices.items) |*device| {
        device.deinit(self.state.gpa);
    }
    self.devices.deinit();
    return self;
}

pub fn module(self: *Battery) Module {
    return .{
pub fn module(self: *Battery) !Module {
    return Module{
        .impl = @ptrCast(*anyopaque, self),
        .eventFn = getEvent,
        .printFn = print,
        .funcs = .{
            .getEvent = getEvent,
            .print = print,
            .destroy = destroy,
        },
    };
}



@@ 86,6 77,36 @@ pub fn getEvent(self_opaque: *anyopaque) !Event {
    };
}

pub fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void {
    const self = utils.cast(Battery)(self_opaque);

    try updateDevices(self.state.gpa, self.context, &self.devices);
    const device = self.devices.items[0];

    var icon: []const u8 = "❓";
    if (mem.eql(u8, device.status, "Discharging")) {
        icon = "🔋";
    } else if (mem.eql(u8, device.status, "Charging")) {
        icon = "🔌";
    } else if (mem.eql(u8, device.status, "Full")) {
        icon = "⚡";
    }

    try fmt.format(writer, "{s}   {d}%", .{ icon, device.capacity });
}

pub fn destroy(self_opaque: *anyopaque) void {
    const self = utils.cast(Battery)(self_opaque);

    _ = self.context.unref();
    for (self.devices.items) |*device| {
        self.state.gpa.free(device.name);
        self.state.gpa.free(device.status);
    }
    self.devices.deinit();
    self.state.gpa.destroy(self);
}

fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    const self = utils.cast(Battery)(self_opaque);



@@ 105,24 126,6 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    }
}

pub fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void {
    const self = utils.cast(Battery)(self_opaque);

    try updateDevices(self.state.gpa, self.context, &self.devices);
    const device = self.devices.items[0];

    var icon: []const u8 = "❓";
    if (mem.eql(u8, device.status, "Discharging")) {
        icon = "🔋";
    } else if (mem.eql(u8, device.status, "Charging")) {
        icon = "🔌";
    } else if (mem.eql(u8, device.status, "Full")) {
        icon = "⚡";
    }

    try fmt.format(writer, "{s}   {d}%", .{ icon, device.capacity });
}

fn updateDevices(
    gpa: mem.Allocator,
    context: *udev.Udev,

M src/render.zig => src/render.zig +1 -1
@@ 121,7 121,7 @@ pub fn renderModules(bar: *Bar) !void {
    defer string.deinit();

    const writer = string.writer();
    for (state.modules.items) |*module| {
    for (state.modules.modules.items) |*module| {
        try std.fmt.format(writer, " | ", .{});
        try module.print(writer);
    }