~andreafeletto/levee

27e73da715bc9ace58bef0d1082cc364a8a35064 — Andrea Feletto a month ago 7c8a37d
major refactor and add alsa module
17 files changed, 1080 insertions(+), 787 deletions(-)

M README.md
M build.zig
M deps/zig-wayland
R src/{shm.zig => Buffer.zig}
A src/Loop.zig
A src/Surface.zig
R src/{tags.zig => Tags.zig}
D src/c.zig
D src/event.zig
M src/main.zig
M src/modules.zig
A src/modules/Alsa.zig
A src/modules/Backlight.zig
A src/modules/Battery.zig
M src/render.zig
A src/utils.zig
M src/wayland.zig
M README.md => README.md +0 -1
@@ 9,7 9,6 @@ Some important things are not implemented yet:

* configuration via cli flags
* configuration via config file
* volume information

## Build


M build.zig => build.zig +15 -23
@@ 7,12 7,19 @@ pub fn build(b: *std.build.Builder) void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardReleaseOptions();

    const exe = b.addExecutable("levee", "src/main.zig");
    exe.setTarget(target);
    exe.setBuildMode(mode);

    const scanner = ScanProtocolsStep.create(b);
    scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");
    scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml");
    scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");
    scanner.addProtocolPath("protocol/river-control-unstable-v1.xml");

    exe.step.dependOn(&scanner.step);
    scanner.addCSource(exe);

    const wayland = Pkg{
        .name = "wayland",
        .path = .{ .generated = &scanner.result },


@@ 31,25 38,17 @@ pub fn build(b: *std.build.Builder) void {
        .path = .{ .path = "deps/zig-udev/udev.zig" },
    };

    const exe = b.addExecutable("levee", "src/main.zig");
    exe.setTarget(target);
    exe.setBuildMode(mode);

    exe.linkLibC();

    exe.addPackage(wayland);
    exe.linkSystemLibrary("wayland-client");
    exe.step.dependOn(&scanner.step);
    scanner.addCSource(exe);

    exe.addPackage(fcft);
    exe.addPackage(pixman);
    exe.linkSystemLibrary("pixman-1");
    exe.addPackage(udev);
    exe.addPackage(wayland);

    exe.addPackage(fcft);
    exe.linkLibC();
    exe.linkSystemLibrary("alsa");
    exe.linkSystemLibrary("fcft");

    exe.addPackage(udev);
    exe.linkSystemLibrary("libudev");
    exe.linkSystemLibrary("pixman-1");
    exe.linkSystemLibrary("wayland-client");

    exe.install();



@@ 59,13 58,6 @@ pub fn build(b: *std.build.Builder) void {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    const run_step = b.step("run", "Run levee");
    run_step.dependOn(&run_cmd.step);

    const exe_tests = b.addTest("src/main.zig");
    exe_tests.setTarget(target);
    exe_tests.setBuildMode(mode);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&exe_tests.step);
}

M deps/zig-wayland => deps/zig-wayland +1 -1
@@ 1,1 1,1 @@
Subproject commit 753dfe81d9986ffe206a66d175a971bcaf37b7ea
Subproject commit 75802aa74ae7afcc99700bfd752df2ef5272137a

R src/shm.zig => src/Buffer.zig +46 -49
@@ 2,68 2,65 @@ const std = @import("std");
const mem = std.mem;
const os = std.os;

const wayland = @import("wayland").client;
const wl = wayland.wl;

const pixman = @import("pixman");
const wl = @import("wayland").client.wl;

pub const Buffer = struct {
    data: ?[]align(4096) u8,
    buffer: ?*wl.Buffer,
    pix: ?*pixman.Image,
const Buffer = @This();

    busy: bool,
    width: u31,
    height: u31,
    size: u31,
data: ?[]align(4096) u8,
buffer: ?*wl.Buffer,
pix: ?*pixman.Image,
busy: bool,
width: u31,
height: u31,
size: u31,

    pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void {
        self.busy = true;
        self.width = width;
        self.height = height;
pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void {
    self.busy = true;
    self.width = width;
    self.height = height;

        const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0);
        defer os.close(fd);
    const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0);
    defer os.close(fd);

        const stride = width * 4;
        self.size = stride * height;
        try os.ftruncate(fd, self.size);
    const stride = width * 4;
    self.size = stride * height;
    try os.ftruncate(fd, self.size);

        self.data = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0);
        errdefer os.munmap(self.data.?);
    self.data = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0);
    errdefer os.munmap(self.data.?);

        const pool = try shm.createPool(fd, self.size);
        defer pool.destroy();
    const pool = try shm.createPool(fd, self.size);
    defer pool.destroy();

        self.buffer = try pool.createBuffer(0, width, height, stride, .argb8888);
        errdefer self.buffer.?.destroy();
        self.buffer.?.setListener(*Buffer, listener, self);
    self.buffer = try pool.createBuffer(0, width, height, stride, .argb8888);
    errdefer self.buffer.?.destroy();
    self.buffer.?.setListener(*Buffer, listener, self);

        self.pix = pixman.Image.createBitsNoClear(.a8r8g8b8, width, height, @ptrCast([*c]u32, self.data.?), stride);
    }
    self.pix = pixman.Image.createBitsNoClear(.a8r8g8b8, width, height, @ptrCast([*c]u32, self.data.?), stride);
}

    pub fn deinit(self: *Buffer) void {
        if (self.pix) |pix| _ = pix.unref();
        if (self.buffer) |buf| buf.destroy();
        if (self.data) |data| os.munmap(data);
    }
pub fn deinit(self: *Buffer) void {
    if (self.pix) |pix| _ = pix.unref();
    if (self.buffer) |buf| buf.destroy();
    if (self.data) |data| os.munmap(data);
}

    fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void {
        switch (event) {
            .release => buffer.busy = false,
        }
fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void {
    switch (event) {
        .release => buffer.busy = false,
    }
}

    pub fn nextBuffer(pool: *[2]Buffer, shm: *wl.Shm, width: u16, height: u16) !*Buffer {
        if (pool[0].busy and pool[1].busy) {
            return error.NoAvailableBuffers;
        }
        const buffer = if (!pool[0].busy) &pool[0] else &pool[1];
pub fn nextBuffer(pool: *[2]Buffer, shm: *wl.Shm, width: u16, height: u16) !*Buffer {
    if (pool[0].busy and pool[1].busy) {
        return error.NoAvailableBuffers;
    }
    const buffer = if (!pool[0].busy) &pool[0] else &pool[1];

        if (buffer.width != width or buffer.height != height) {
            buffer.deinit();
            try buffer.init(shm, width, height);
        }
        return buffer;
    if (buffer.width != width or buffer.height != height) {
        buffer.deinit();
        try buffer.init(shm, width, height);
    }
};
    return buffer;
}

A src/Loop.zig => src/Loop.zig +85 -0
@@ 0,0 1,85 @@
const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;
const os = std.os;

const wl = @import("wayland").client.wl;

const Module = @import("modules.zig").Module;
const State = @import("main.zig").State;
const utils = @import("utils.zig");
const Loop = @This();

state: *State,
sfd: os.fd_t,

pub const Event = struct {
    fd: os.pollfd,
    data: *anyopaque,
    callbackIn: Callback,
    callbackOut: Callback,

    pub const Callback = fn (*anyopaque) error{Terminate}!void;

    pub fn terminate(_: *anyopaque) error{Terminate}!void {
        return error.Terminate;
    }

    pub fn noop(_: *anyopaque) error{Terminate}!void {
        return;
    }
};

pub fn init(state: *State) !Loop {
    var mask = mem.zeroes(os.linux.sigset_t);
    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);

    return Loop{ .state = state, .sfd = @intCast(os.fd_t, sfd) };
}

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);

    try events.append(gpa, .{
        .fd = .{ .fd = self.sfd, .events = os.POLL.IN, .revents = 0 },
        .data = undefined,
        .callbackIn = Event.terminate,
        .callbackOut = Event.noop,
    });
    try events.append(gpa, try self.state.wayland.getEvent());
    for (self.state.modules.items) |*module| {
        try events.append(gpa, try module.getEvent());
    }

    const fds = events.items(.fd);
    while (true) {
        while (true) {
            const ret = try display.dispatchPending();
            _ = try display.flush();
            if (ret <= 0) break;
        }
        _ = try os.poll(fds, -1);

        for (fds) |fd, i| {
            if (fd.revents & os.POLL.HUP != 0) return;
            if (fd.revents & os.POLL.ERR != 0) return;

            if (fd.revents & os.POLL.IN != 0) {
                const event = events.get(i);
                event.callbackIn(event.data) catch return;
            }
            if (fd.revents & os.POLL.OUT != 0) {
                const event = events.get(i);
                event.callbackOut(event.data) catch return;
            }
        }
    }
}

A src/Surface.zig => src/Surface.zig +146 -0
@@ 0,0 1,146 @@
const std = @import("std");
const mem = std.mem;

const wl = @import("wayland").client.wl;
const zwlr = @import("wayland").client.zwlr;

const Buffer = @import("Buffer.zig");
const Monitor = @import("wayland.zig").Monitor;
const render = @import("render.zig");
const Surface = @This();

monitor: *Monitor,

backgroundSurface: *wl.Surface,
layerSurface: *zwlr.LayerSurfaceV1,
backgroundBuffers: [2]Buffer,

tagsSurface: *wl.Surface,
tagsSubsurface: *wl.Subsurface,
tagsBuffers: [2]Buffer,

clockSurface: *wl.Surface,
clockSubsurface: *wl.Subsurface,
clockBuffers: [2]Buffer,

modulesSurface: *wl.Surface,
modulesSubsurface: *wl.Subsurface,
modulesBuffers: [2]Buffer,

configured: bool,
width: u16,
height: u16,

pub fn create(monitor: *Monitor) !*Surface {
    const state = monitor.state;
    const globals = &state.wayland.globals;

    const self = try state.gpa.create(Surface);
    self.monitor = monitor;
    self.configured = false;

    self.backgroundSurface = try globals.compositor.createSurface();
    self.layerSurface = try globals.layerShell.getLayerSurface(
        self.backgroundSurface,
        monitor.output,
        .top,
        "levee",
    );
    self.backgroundBuffers = mem.zeroes([2]Buffer);

    self.tagsSurface = try globals.compositor.createSurface();
    self.tagsSubsurface = try globals.subcompositor.getSubsurface(
        self.tagsSurface,
        self.backgroundSurface,
    );
    self.tagsBuffers = mem.zeroes([2]Buffer);

    self.clockSurface = try globals.compositor.createSurface();
    self.clockSubsurface = try globals.subcompositor.getSubsurface(
        self.clockSurface,
        self.backgroundSurface,
    );
    self.clockBuffers = mem.zeroes([2]Buffer);

    self.modulesSurface = try globals.compositor.createSurface();
    self.modulesSubsurface = try globals.subcompositor.getSubsurface(
        self.modulesSurface,
        self.backgroundSurface,
    );
    self.modulesBuffers = mem.zeroes([2]Buffer);

    // setup layer surface
    self.layerSurface.setSize(0, state.config.height);
    self.layerSurface.setAnchor(
        .{ .top = true, .left = true, .right = true, .bottom = false },
    );
    self.layerSurface.setExclusiveZone(state.config.height);
    self.layerSurface.setMargin(0, 0, 0, 0);
    self.layerSurface.setListener(*Surface, layerSurfaceListener, self);

    // setup subsurfaces
    self.tagsSubsurface.setPosition(0, 0);
    self.clockSubsurface.setPosition(0, 0);
    self.modulesSubsurface.setPosition(0, 0);

    self.tagsSurface.commit();
    self.clockSurface.commit();
    self.backgroundSurface.commit();

    return self;
}

pub fn destroy(self: *Surface) void {
    self.monitor.surface = null;

    self.backgroundSurface.destroy();
    self.layerSurface.destroy();
    self.backgroundBuffers[0].deinit();
    self.backgroundBuffers[1].deinit();

    self.tagsSurface.destroy();
    self.tagsSubsurface.destroy();
    self.tagsBuffers[0].deinit();
    self.tagsBuffers[1].deinit();

    self.clockSurface.destroy();
    self.clockSubsurface.destroy();
    self.clockBuffers[0].deinit();
    self.clockBuffers[1].deinit();

    self.modulesSurface.destroy();
    self.modulesSubsurface.destroy();
    self.modulesBuffers[0].deinit();
    self.modulesBuffers[1].deinit();

    self.monitor.state.gpa.destroy(self);
}

fn layerSurfaceListener(
    layerSurface: *zwlr.LayerSurfaceV1,
    event: zwlr.LayerSurfaceV1.Event,
    surface: *Surface,
) void {
    switch (event) {
        .configure => |data| {
            surface.configured = true;
            surface.width = @intCast(u16, data.width);
            surface.height = @intCast(u16, data.height);

            layerSurface.ackConfigure(data.serial);

            render.renderBackground(surface) catch return;
            render.renderTags(surface) catch return;
            render.renderClock(surface) catch return;
            render.renderModules(surface) catch return;

            surface.tagsSurface.commit();
            surface.clockSurface.commit();
            surface.modulesSurface.commit();
            surface.backgroundSurface.commit();
        },
        .closed => {
            surface.destroy();
        },
    }
}

R src/tags.zig => src/Tags.zig +66 -66
@@ 2,10 2,15 @@ const std = @import("std");

const zriver = @import("wayland").client.zriver;

const Output = @import("wayland.zig").Output;
const Monitor = @import("wayland.zig").Monitor;
const render = @import("render.zig");
const Seat = @import("wayland.zig").Seat;
const Input = @import("wayland.zig").Input;
const State = @import("main.zig").State;
const Tags = @This();

monitor: *Monitor,
outputStatus: *zriver.OutputStatusV1,
tags: [9]Tag,

pub const Tag = struct {
    label: u8,


@@ 13,81 18,76 @@ pub const Tag = struct {
    occupied: bool = false,
};

pub const Tags = struct {
    output: *Output,
    outputStatus: *zriver.OutputStatusV1,
    tags: [9]Tag,

    pub fn create(state: *State, output: *Output) !*Tags {
        const self = try state.allocator.create(Tags);
        const wayland = state.wayland;

        self.output = output;
        self.outputStatus = try wayland.statusManager.getRiverOutputStatus(
            output.wlOutput,
        );
        for (self.tags) |*tag, i| {
            tag.label = '1' + @intCast(u8, i);
        }
pub fn create(state: *State, monitor: *Monitor) !*Tags {
    const self = try state.gpa.create(Tags);
    const globals = &state.wayland.globals;

        self.outputStatus.setListener(*Tags, outputStatusListener, self);
        return self;
    self.monitor = monitor;
    self.outputStatus = try globals.statusManager.getRiverOutputStatus(
        monitor.output,
    );
    for (self.tags) |*tag, i| {
        tag.label = '1' + @intCast(u8, i);
    }

    pub fn destroy(self: *Tags) void {
        self.outputStatus.destroy();
    }
    self.outputStatus.setListener(*Tags, outputStatusListener, self);
    return self;
}

    fn outputStatusListener(
        _: *zriver.OutputStatusV1,
        event: zriver.OutputStatusV1.Event,
        tags: *Tags,
    ) void {
        switch (event) {
            .focused_tags => |data| {
pub fn destroy(self: *Tags) void {
    self.outputStatus.destroy();
    self.monitor.state.gpa.destroy(self);
}

fn outputStatusListener(
    _: *zriver.OutputStatusV1,
    event: zriver.OutputStatusV1.Event,
    tags: *Tags,
) void {
    switch (event) {
        .focused_tags => |data| {
            for (tags.tags) |*tag, i| {
                const mask = @as(u32, 1) << @intCast(u5, i);
                tag.focused = data.tags & mask != 0;
            }
        },
        .view_tags => |data| {
            for (tags.tags) |*tag| {
                tag.occupied = false;
            }
            for (data.tags.slice(u32)) |view| {
                for (tags.tags) |*tag, i| {
                    const mask = @as(u32, 1) << @intCast(u5, i);
                    tag.focused = data.tags & mask != 0;
                }
            },
            .view_tags => |data| {
                for (tags.tags) |*tag| {
                    tag.occupied = false;
                    if (view & mask != 0) tag.occupied = true;
                }
                for (data.tags.slice(u32)) |view| {
                    for (tags.tags) |*tag, i| {
                        const mask = @as(u32, 1) << @intCast(u5, i);
                        if (view & mask != 0) tag.occupied = true;
                    }
                }
            },
        }
        if (tags.output.surface) |surface| {
            if (surface.configured) {
                render.renderTags(surface) catch return;
                surface.tagsSurface.commit();
                surface.backgroundSurface.commit();
            }
        },
    }
    if (tags.monitor.surface) |surface| {
        if (surface.configured) {
            render.renderTags(surface) catch return;
            surface.tagsSurface.commit();
            surface.backgroundSurface.commit();
        }
    }
}

    pub fn handleClick(self: *Tags, x: u32, seat: *Seat) !void {
        const state = self.output.state;
        const control = state.wayland.control;
pub fn handleClick(self: *Tags, x: u32, input: *Input) !void {
    const state = self.monitor.state;
    const control = state.wayland.globals.control;

        if (self.output.surface) |surface| {
            const index = x / surface.height;
            const payload = try std.fmt.allocPrintZ(
                state.allocator,
                "{d}",
                .{@as(u32, 1) << @intCast(u5, index)},
            );
            defer state.allocator.free(payload);
    if (self.monitor.surface) |surface| {
        const index = x / surface.height;
        const payload = try std.fmt.allocPrintZ(
            state.gpa,
            "{d}",
            .{@as(u32, 1) << @intCast(u5, index)},
        );
        defer state.gpa.free(payload);

            control.addArgument("set-focused-tags");
            control.addArgument(payload);
            const callback = try control.runCommand(seat.wlSeat);
            _ = callback;
        }
        control.addArgument("set-focused-tags");
        control.addArgument(payload);
        const callback = try control.runCommand(input.seat);
        _ = callback;
    }
};
}

D src/c.zig => src/c.zig +0 -1
@@ 1,1 0,0 @@
pub const time = @cImport(@cInclude("time.h"));

D src/event.zig => src/event.zig +0 -135
@@ 1,135 0,0 @@
const std = @import("std");
const mem = std.mem;
const os = std.os;
const ArrayList = std.ArrayList;

const wl = @import("wayland").client.wl;
const udev = @import("udev");

const render = @import("render.zig");
const State = @import("main.zig").State;

pub const Loop = struct {
    state: *State,
    fds: [4]os.pollfd,
    monitor: *udev.Monitor,

    pub fn init(state: *State) !Loop {
        // signals
        var mask = mem.zeroes(os.linux.sigset_t);
        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);

        // wayland
        const wfd = state.wayland.display.getFd();

        // timer
        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 },
        };
        _ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null);

        // udev
        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();
        const ufd = try monitor.getFd();

        // poll fds
        const fds: [4]os.fd_t = .{
            @intCast(os.fd_t, sfd),
            @intCast(os.fd_t, wfd),
            @intCast(os.fd_t, tfd),
            @intCast(os.fd_t, ufd),
        };
        var pfds: [4]os.pollfd = undefined;
        for (fds) |fd, i| {
            pfds[i] = .{ .fd = fd, .events = os.POLL.IN, .revents = 0 };
        }

        return Loop{
            .state = state,
            .fds = pfds,
            .monitor = monitor,
        };
    }

    pub fn run(self: *Loop) !void {
        const display = self.state.wayland.display;

        while (true) {
            while (true) {
                const ret = try display.dispatchPending();
                _ = try display.flush();
                if (ret <= 0) break;
            }
            _ = try os.poll(&self.fds, -1);

            for (self.fds) |fd| {
                if (fd.revents & os.POLL.HUP != 0) {
                    return;
                }
                if (fd.revents & os.POLL.ERR != 0) {
                    return;
                }
            }

            // signals
            if (self.fds[0].revents & os.POLL.IN != 0) {
                return;
            }

            // wayland
            if (self.fds[1].revents & os.POLL.IN != 0) {
                _ = try display.dispatch();
            }
            if (self.fds[1].revents & os.POLL.OUT != 0) {
                _ = try display.flush();
            }

            // timer
            if (self.fds[2].revents & os.POLL.IN != 0) {
                const tfd = self.fds[2].fd;
                var expirations = mem.zeroes([8]u8);
                _ = try os.read(tfd, &expirations);

                for (self.state.wayland.outputs.items) |output| {
                    if (output.surface) |surface| {
                        if (surface.configured) {
                            render.renderClock(surface) catch continue;
                            render.renderModules(surface) catch continue;
                            surface.clockSurface.commit();
                            surface.modulesSurface.commit();
                            surface.backgroundSurface.commit();
                        }
                    }
                }
            }

            // udev
            if (self.fds[3].revents & os.POLL.IN != 0) {
                _ = try self.monitor.receiveDevice();

                for (self.state.wayland.outputs.items) |output| {
                    if (output.surface) |surface| {
                        if (surface.configured) {
                            render.renderModules(surface) catch continue;
                            surface.modulesSurface.commit();
                            surface.backgroundSurface.commit();
                        }
                    }
                }
            }
        }
    }
};

M src/main.zig => src/main.zig +22 -11
@@ 1,46 1,57 @@
const std = @import("std");
const heap = std.heap;
const mem = std.mem;

const fcft = @import("fcft");

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

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

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

pub fn main() anyerror!void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    var gpa: heap.GeneralPurposeAllocator(.{}) = .{};
    defer _ = gpa.deinit();

    fcft.init(.auto, false, .info);

    // initialization
    var state: State = undefined;
    state.allocator = arena.allocator();
    state.gpa = gpa.allocator();
    state.config = try Config.init();
    state.wayland = try Wayland.init(&state);
    defer state.wayland.deinit();
    state.loop = try Loop.init(&state);

    // modules
    state.modules = std.ArrayList(modules.Module).init(state.allocator);
    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);
    try state.modules.append(state.backlight.module());
    defer state.backlight.deinit();
    state.battery = try modules.Battery.init(&state);
    try state.modules.append(state.battery.module());
    defer state.battery.deinit();

    // wayland
    try state.wayland.registerGlobals();
    try state.modules.appendSlice(&.{
        try state.backlight.module(),
        state.battery.module(),
        state.alsa.module(),
    });

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

M src/modules.zig => src/modules.zig +10 -227
@@ 1,241 1,24 @@
const std = @import("std");
const fmt = std.fmt;
const fs = std.fs;
const io = std.io;
const mem = std.mem;
const os = std.os;

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

const State = @import("main.zig").State;

const StringWriter = std.ArrayList(u8).Writer;
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 fn print(self: *Module, writer: StringWriter) !void {
        try self.printFn(self.impl, writer);
    }

    pub fn cast(comptime to: type) fn (*anyopaque) *to {
        return (struct {
            pub fn cast(module: *anyopaque) *to {
                return @ptrCast(*to, @alignCast(@alignOf(to), module));
            }
        }).cast;
    }
};

pub const Battery = struct {
    state: *State,
    context: *udev.Udev,
    devices: DeviceList,

    const Device = struct {
        name: []const u8,
        status: []const u8,
        capacity: u8,
    };
    const DeviceList = std.ArrayList(Device);

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

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

        return Battery{
            .state = state,
            .context = context,
            .devices = devices,
        };
    }

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

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

        try updateDevices(self.state.allocator, 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(
        allocator: mem.Allocator,
        context: *udev.Udev,
        devices: *DeviceList,
    ) !void {
        const enumerate = try udev.Enumerate.new(context);
        try enumerate.addMatchSubsystem("power_supply");
        try enumerate.addMatchSysattr("type", "Battery");
        try enumerate.scanDevices();
        const entries = enumerate.getListEntry();

        var maybe_entry = entries;
        while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) {
            const path = entry.getName();
            const device = try udev.Device.newFromSyspath(context, path);
            try updateOrAppend(allocator, devices, device);
        }
    }

    fn updateOrAppend(
        allocator: mem.Allocator,
        devices: *DeviceList,
        dev: *udev.Device,
    ) !void {
        const name = dev.getSysname() catch return;
        const status = dev.getSysattrValue("status") catch return;
        const capacity = getCapacity(dev) catch return;

        const device = blk: {
            for (devices.items) |*device| {
                if (mem.eql(u8, device.name, name)) {
                    break :blk device;
                }
            } else {
                const device = try devices.addOne();
                device.name = try allocator.dupe(u8, name);
                break :blk device;
            }
        };

        device.status = try allocator.dupe(u8, status);
        device.capacity = capacity;
    }

    fn getCapacity(dev: *udev.Device) !u8 {
        const capacity_str = dev.getSysattrValue("capacity") catch {
            return computeCapacityFromCharge(dev) catch {
                return computeCapacityFromEnergy(dev);
            };
        };

        const capacity = try fmt.parseInt(u8, capacity_str, 10);
        return capacity;
    }

    fn computeCapacityFromEnergy(dev: *udev.Device) !u8 {
        const energy_str = try dev.getSysattrValue("energy_now");
        const energy_full_str = try dev.getSysattrValue("energy_full");

        const energy = try fmt.parseFloat(f64, energy_str);
        const energy_full = try fmt.parseFloat(f64, energy_full_str);

        const capacity = energy * 100.0 / energy_full;
        return @floatToInt(u8, @round(capacity));
    }

    fn computeCapacityFromCharge(dev: *udev.Device) !u8 {
        const charge_str = try dev.getSysattrValue("charge_now");
        const charge_full_str = try dev.getSysattrValue("charge_full");

        const charge = try fmt.parseFloat(f64, charge_str);
        const charge_full = try fmt.parseFloat(f64, charge_full_str);
    pub const StringWriter = std.ArrayList(u8).Writer;

        const capacity = charge * 100.0 / charge_full;
        return @floatToInt(u8, @round(capacity));
    pub fn getEvent(self: *Module) !Event {
        return self.eventFn(self.impl);
    }
};

pub const Backlight = struct {
    state: *State,
    context: *udev.Udev,
    devices: DeviceList,

    const Device = struct {
        name: []const u8,
        value: u64,
        max: u64,
    };
    const DeviceList = std.ArrayList(Device);

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

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

        return Backlight{
            .state = state,
            .context = context,
            .devices = devices,
        };
    }

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

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

        try updateDevices(self.state.allocator, self.context, &self.devices);
        const device = self.devices.items[0];
        var percent = @intToFloat(f64, device.value) * 100.0;
        percent /= @intToFloat(f64, device.max);
        const value = @floatToInt(u8, @round(percent));

        try writer.print("💡   {d}%", .{value});
    }

    fn updateDevices(
        allocator: mem.Allocator,
        context: *udev.Udev,
        devices: *DeviceList,
    ) !void {
        const enumerate = try udev.Enumerate.new(context);
        try enumerate.addMatchSubsystem("backlight");
        try enumerate.scanDevices();
        const entries = enumerate.getListEntry();

        var maybe_entry = entries;
        while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) {
            const path = entry.getName();
            const device = try udev.Device.newFromSyspath(context, path);
            try updateOrAppend(allocator, devices, device);
        }
    }

    fn updateOrAppend(
        allocator: mem.Allocator,
        devices: *DeviceList,
        dev: *udev.Device,
    ) !void {
        const value = try dev.getSysattrValue("actual_brightness");
        const max = try dev.getSysattrValue("max_brightness");
        const name = try dev.getSysname();

        const device = blk: {
            for (devices.items) |*device| {
                if (mem.eql(u8, device.name, name)) {
                    break :blk device;
                }
            } else {
                const device = try devices.addOne();
                device.name = try allocator.dupe(u8, name);
                break :blk device;
            }
        };
        device.value = try fmt.parseInt(u64, value, 10);
        device.max = try fmt.parseInt(u64, max, 10);
    pub fn print(self: *Module, writer: StringWriter) !void {
        return self.printFn(self.impl, writer);
    }
};

A src/modules/Alsa.zig => src/modules/Alsa.zig +127 -0
@@ 0,0 1,127 @@
const std = @import("std");
const fmt = std.fmt;
const math = std.math;
const mem = std.mem;
const os = std.os;

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

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

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

pub fn init(state: *State) !Alsa {
    return Alsa{
        .state = state,
        .context = try getAlsaCtl(state.gpa),
    };
}

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

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);

    return Event{
        .fd = @bitCast(os.pollfd, fd),
        .data = self_opaque,
        .callbackIn = callbackIn,
        .callbackOut = Event.noop,
    };
}

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

    var handle: ?*alsa.snd_mixer_t = null;
    _ = alsa.snd_mixer_open(&handle, 0);
    _ = alsa.snd_mixer_attach(handle, "default");
    _ = alsa.snd_mixer_selem_register(handle, null, null);
    _ = alsa.snd_mixer_load(handle);

    var sid: ?*alsa.snd_mixer_selem_id_t = null;
    _ = alsa.snd_mixer_selem_id_malloc(&sid);
    defer alsa.snd_mixer_selem_id_free(sid);
    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(
        elem,
        alsa.SND_MIXER_SCHN_MONO,
        &unmuted,
    );
    if (unmuted == 0) {
        return writer.print("   🔇   ", .{});
    }

    var min: i64 = 0;
    var max: i64 = 0;
    _ = alsa.snd_mixer_selem_get_playback_volume_range(elem, &min, &max);

    var volume: i64 = 0;
    _ = alsa.snd_mixer_selem_get_playback_volume(
        elem,
        alsa.SND_MIXER_SCHN_MONO,
        &volume,
    );

    const percent = percent: {
        var x = @intToFloat(f64, volume) / @intToFloat(f64, max);
        x = math.tanh(math.sqrt(x) * 0.65) * 180.0;
        break :percent @floatToInt(u8, @round(x));
    };
    return writer.print("🔊   {d}%", .{ percent });
}

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

    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);

    for (self.state.wayland.monitors.items) |monitor| {
        if (monitor.surface) |surface| {
            if (surface.configured) {
                render.renderClock(surface) catch continue;
                render.renderModules(surface) catch continue;
                surface.clockSurface.commit();
                surface.modulesSurface.commit();
                surface.backgroundSurface.commit();
            }
        }
    }
}

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);

    return ctl.?;
}

A src/modules/Backlight.zig => src/modules/Backlight.zig +150 -0
@@ 0,0 1,150 @@
const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;
const os = std.os;

const udev = @import("udev");

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

state: *State,
context: *udev.Udev,
monitor: *udev.Monitor,
devices: DeviceList,

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();

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

    return Backlight{
        .state = state,
        .context = context,
        .monitor = monitor,
        .devices = devices,
    };
}

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

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

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

    return Event{
        .fd = .{
            .fd = try self.monitor.getFd(),
            .events = os.POLL.IN,
            .revents = undefined,
        },
        .data = self_opaque,
        .callbackIn = callbackIn,
        .callbackOut = Event.noop,
    };
}

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

    _ = self.monitor.receiveDevice() catch return;
    for (self.state.wayland.monitors.items) |monitor| {
        if (monitor.surface) |surface| {
            if (surface.configured) {
                render.renderModules(surface) catch continue;
                surface.modulesSurface.commit();
                surface.backgroundSurface.commit();
            }
        }
    }
}

pub 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);
    const device = self.devices.items[0];
    var percent = @intToFloat(f64, device.value) * 100.0;
    percent /= @intToFloat(f64, device.max);
    const value = @floatToInt(u8, @round(percent));

    try writer.print("💡   {d}%", .{value});
}

fn updateDevices(
    gpa: mem.Allocator,
    context: *udev.Udev,
    devices: *DeviceList,
) !void {
    const enumerate = try udev.Enumerate.new(context);
    try enumerate.addMatchSubsystem("backlight");
    try enumerate.scanDevices();
    const entries = enumerate.getListEntry();

    var maybe_entry = entries;
    while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) {
        const path = entry.getName();
        const device = try udev.Device.newFromSyspath(context, path);
        try updateOrAppend(gpa, devices, device);
    }
}

fn updateOrAppend(
    gpa: mem.Allocator,
    devices: *DeviceList,
    dev: *udev.Device,
) !void {
    const value = try dev.getSysattrValue("actual_brightness");
    const max = try dev.getSysattrValue("max_brightness");
    const name = try dev.getSysname();

    const device = blk: {
        for (devices.items) |*device| {
            if (mem.eql(u8, device.name, name)) {
                break :blk device;
            }
        } else {
            const device = try devices.addOne();
            device.name = try gpa.dupe(u8, name);
            break :blk device;
        }
    };
    device.value = try fmt.parseInt(u64, value, 10);
    device.max = try fmt.parseInt(u64, max, 10);
}

A src/modules/Battery.zig => src/modules/Battery.zig +205 -0
@@ 0,0 1,205 @@
const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;
const os = std.os;

const udev = @import("udev");

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

state: *State,
context: *udev.Udev,
timerFd: os.fd_t,
devices: DeviceList,

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 },
    };
    _ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null);

    const 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;

    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();
}

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

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

    return Event{
        .fd = .{
            .fd = self.timerFd,
            .events = os.POLL.IN,
            .revents = undefined,
        },
        .data = self_opaque,
        .callbackIn = callbackIn,
        .callbackOut = Event.noop,
    };
}

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

    var expirations = mem.zeroes([8]u8);
    _ = os.read(self.timerFd, &expirations) catch return;

    for (self.state.wayland.monitors.items) |monitor| {
        if (monitor.surface) |surface| {
            if (surface.configured) {
                render.renderClock(surface) catch continue;
                render.renderModules(surface) catch continue;
                surface.clockSurface.commit();
                surface.modulesSurface.commit();
                surface.backgroundSurface.commit();
            }
        }
    }
}

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,
    devices: *DeviceList,
) !void {
    const enumerate = try udev.Enumerate.new(context);
    defer _ = enumerate.unref();

    try enumerate.addMatchSubsystem("power_supply");
    try enumerate.addMatchSysattr("type", "Battery");
    try enumerate.scanDevices();

    const entries = enumerate.getListEntry();

    var maybe_entry = entries;
    while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) {
        const path = entry.getName();
        const device = try udev.Device.newFromSyspath(context, path);
        try updateOrAppend(gpa, devices, device);
    }
}

fn updateOrAppend(
    gpa: mem.Allocator,
    devices: *DeviceList,
    dev: *udev.Device,
) !void {
    const name = dev.getSysname() catch return;
    const status = dev.getSysattrValue("status") catch return;
    const capacity = getCapacity(dev) catch return;

    const device = blk: {
        for (devices.items) |*device| {
            if (mem.eql(u8, device.name, name)) {
                gpa.free(device.status);
                break :blk device;
            }
        } else {
            const device = try devices.addOne();
            device.name = try gpa.dupe(u8, name);
            break :blk device;
        }
    };

    device.status = try gpa.dupe(u8, status);
    device.capacity = capacity;
}

fn getCapacity(dev: *udev.Device) !u8 {
    const capacity_str = dev.getSysattrValue("capacity") catch {
        return computeCapacityFromCharge(dev) catch {
            return computeCapacityFromEnergy(dev);
        };
    };

    const capacity = try fmt.parseInt(u8, capacity_str, 10);
    return capacity;
}

fn computeCapacityFromEnergy(dev: *udev.Device) !u8 {
    const energy_str = try dev.getSysattrValue("energy_now");
    const energy_full_str = try dev.getSysattrValue("energy_full");

    const energy = try fmt.parseFloat(f64, energy_str);
    const energy_full = try fmt.parseFloat(f64, energy_full_str);

    const capacity = energy * 100.0 / energy_full;
    return @floatToInt(u8, @round(capacity));
}

fn computeCapacityFromCharge(dev: *udev.Device) !u8 {
    const charge_str = try dev.getSysattrValue("charge_now");
    const charge_full_str = try dev.getSysattrValue("charge_full");

    const charge = try fmt.parseFloat(f64, charge_str);
    const charge_full = try fmt.parseFloat(f64, charge_full_str);

    const capacity = charge * 100.0 / charge_full;
    return @floatToInt(u8, @round(capacity));
}

M src/render.zig => src/render.zig +32 -31
@@ 3,23 3,22 @@ const mem = std.mem;

const fcft = @import("fcft");
const pixman = @import("pixman");
const time = @cImport(@cInclude("time.h"));

const Buffer = @import("shm.zig").Buffer;
const c = @import("c.zig");
const Buffer = @import("Buffer.zig");
const State = @import("main.zig").State;
const Surface = @import("wayland.zig").Surface;
const Tag = @import("tags.zig").Tag;
const Tags = @import("tags.zig").Tags;
const Surface = @import("Surface.zig");
const Tag = @import("Tags.zig").Tag;

pub const RenderFn = fn (*Surface) anyerror!void;

pub fn renderBackground(surface: *Surface) !void {
    const state = surface.output.state;
    const state = surface.monitor.state;
    const wlSurface = surface.backgroundSurface;

    const buffer = try Buffer.nextBuffer(
        &surface.backgroundBuffers,
        state.wayland.shm,
        state.wayland.globals.shm,
        surface.width,
        surface.height,
    );


@@ 31,19 30,19 @@ pub fn renderBackground(surface: *Surface) !void {
    const color = &state.config.backgroundColor;
    _ = pixman.Image.fillRectangles(.src, buffer.pix.?, color, 1, &area);

    wlSurface.setBufferScale(surface.output.scale);
    wlSurface.setBufferScale(surface.monitor.scale);
    wlSurface.damageBuffer(0, 0, surface.width, surface.height);
    wlSurface.attach(buffer.buffer, 0, 0);
}

pub fn renderTags(surface: *Surface) !void {
    const state = surface.output.state;
    const state = surface.monitor.state;
    const wlSurface = surface.tagsSurface;
    const tags = surface.output.tags.tags;
    const tags = surface.monitor.tags.tags;

    const buffer = try Buffer.nextBuffer(
        &surface.tagsBuffers,
        surface.output.state.wayland.shm,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
    );


@@ 54,18 53,18 @@ pub fn renderTags(surface: *Surface) !void {
        try renderTag(buffer.pix.?, tag, surface.height, offset, state);
    }

    wlSurface.setBufferScale(surface.output.scale);
    wlSurface.setBufferScale(surface.monitor.scale);
    wlSurface.damageBuffer(0, 0, surface.width, surface.height);
    wlSurface.attach(buffer.buffer, 0, 0);
}

pub fn renderClock(surface: *Surface) !void {
    const state = surface.output.state;
    const state = surface.monitor.state;
    const wlSurface = surface.clockSurface;

    const buffer = try Buffer.nextBuffer(
        &surface.clockBuffers,
        surface.output.state.wayland.shm,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
    );


@@ 80,20 79,20 @@ pub fn renderClock(surface: *Surface) !void {

    // get formatted datetime
    const str = try formatDatetime(state);
    defer state.allocator.free(str);
    defer state.gpa.free(str);

    // ut8 encoding
    const utf8 = try std.unicode.Utf8View.init(str);
    var utf8_iter = utf8.iterator();

    var runes = try state.allocator.alloc(u32, str.len);
    defer state.allocator.free(runes);
    var runes = try state.gpa.alloc(u32, str.len);
    defer state.gpa.free(runes);

    var i: usize = 0;
    while (utf8_iter.nextCodepoint()) |rune| : (i += 1) {
        runes[i] = rune;
    }
    runes = state.allocator.resize(runes, i).?;
    runes = state.gpa.resize(runes, i).?;

    const run = try fcft.TextRun.rasterizeUtf32(
        state.config.font,


@@ 122,25 121,27 @@ pub fn renderClock(surface: *Surface) !void {
        x_offset += glyph.advance.x;
    }

    wlSurface.setBufferScale(surface.output.scale);
    wlSurface.setBufferScale(surface.monitor.scale);
    wlSurface.damageBuffer(0, 0, surface.width, surface.height);
    wlSurface.attach(buffer.buffer, 0, 0);
}

pub fn renderModules(surface: *Surface) !void {
    const state = surface.output.state;
    const state = surface.monitor.state;
    const wlSurface = surface.modulesSurface;

    const buffer = try Buffer.nextBuffer(
        &surface.modulesBuffers,
        surface.output.state.wayland.shm,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
    );
    buffer.busy = true;

    // compose string
    var string = std.ArrayList(u8).init(state.allocator);
    var string = std.ArrayList(u8).init(state.gpa);
    defer string.deinit();

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


@@ 151,14 152,14 @@ pub fn renderModules(surface: *Surface) !void {
    const utf8 = try std.unicode.Utf8View.init(string.items);
    var utf8_iter = utf8.iterator();

    var runes = try state.allocator.alloc(u32, string.items.len);
    defer state.allocator.free(runes);
    var runes = try state.gpa.alloc(u32, string.items.len);
    defer state.gpa.free(runes);

    var i: usize = 0;
    while (utf8_iter.nextCodepoint()) |rune| : (i += 1) {
        runes[i] = rune;
    }
    runes = state.allocator.resize(runes, i).?;
    runes = state.gpa.resize(runes, i).?;

    // clear the buffer
    const bg_area = [_]pixman.Rectangle16{


@@ 196,7 197,7 @@ pub fn renderModules(surface: *Surface) !void {
        x_offset += glyph.advance.x;
    }

    wlSurface.setBufferScale(surface.output.scale);
    wlSurface.setBufferScale(surface.monitor.scale);
    wlSurface.damageBuffer(0, 0, surface.width, surface.height);
    wlSurface.attach(buffer.buffer, 0, 0);
}


@@ 248,14 249,14 @@ fn renderTag(
}

fn formatDatetime(state: *State) ![]const u8 {
    var buf = try state.allocator.alloc(u8, 256);
    const now = c.time.time(null);
    const local = c.time.localtime(&now);
    const len = c.time.strftime(
    var buf = try state.gpa.alloc(u8, 256);
    const now = time.time(null);
    const local = time.localtime(&now);
    const len = time.strftime(
        buf.ptr,
        buf.len,
        state.config.clockFormat,
        local,
    );
    return state.allocator.resize(buf, len).?;
    return state.gpa.resize(buf, len).?;
}

A src/utils.zig => src/utils.zig +15 -0
@@ 0,0 1,15 @@
const std = @import("std");
const meta = std.meta;

pub fn cast(comptime to: type) fn (*anyopaque) *to {
    return (struct {
        pub fn cast(module: *anyopaque) *to {
            return @ptrCast(*to, @alignCast(@alignOf(to), module));
        }
    }).cast;
}

pub fn Mask(comptime container: type) type {
    const len = meta.fields(container).len;
    return [len]bool;
}

M src/wayland.zig => src/wayland.zig +160 -242
@@ 1,5 1,7 @@
const std = @import("std");
const mem = std.mem;
const meta = std.meta;
const os = std.os;
const strcmp = std.cstr.cmp;
const ArrayList = std.ArrayList;



@@ 7,27 9,33 @@ const wl = @import("wayland").client.wl;
const zwlr = @import("wayland").client.zwlr;
const zriver = @import("wayland").client.zriver;

const Buffer = @import("shm.zig").Buffer;
const Buffer = @import("Buffer.zig");
const Event = @import("Loop.zig").Event;
const render = @import("render.zig");
const State = @import("main.zig").State;
const Tags = @import("tags.zig").Tags;
const Surface = @import("Surface.zig");
const Tags = @import("Tags.zig");
const utils = @import("utils.zig");

pub const Wayland = struct {
    state: *State,
    display: *wl.Display,
    registry: *wl.Registry,

    outputs: ArrayList(*Output),
    seats: ArrayList(*Seat),

    compositor: *wl.Compositor,
    subcompositor: *wl.Subcompositor,
    shm: *wl.Shm,
    layerShell: *zwlr.LayerShellV1,
    statusManager: *zriver.StatusManagerV1,
    control: *zriver.ControlV1,

    globalsRegistered: [6]bool,
    monitors: ArrayList(*Monitor),
    inputs: ArrayList(*Input),
    globals: Globals,
    globalsMask: GlobalsMask,

    const Globals = struct {
        compositor: *wl.Compositor,
        subcompositor: *wl.Subcompositor,
        shm: *wl.Shm,
        layerShell: *zwlr.LayerShellV1,
        statusManager: *zriver.StatusManagerV1,
        control: *zriver.ControlV1,
    };
    const GlobalsMask = utils.Mask(Globals);

    pub fn init(state: *State) !Wayland {
        const display = try wl.Display.connect(null);


@@ 36,73 44,78 @@ pub const Wayland = struct {
            .state = state,
            .display = display,
            .registry = try display.getRegistry(),
            .outputs = ArrayList(*Output).init(state.allocator),
            .seats = ArrayList(*Seat).init(state.allocator),
            .compositor = undefined,
            .subcompositor = undefined,
            .shm = undefined,
            .layerShell = undefined,
            .statusManager = undefined,
            .control = undefined,
            .globalsRegistered = mem.zeroes([6]bool),
            .monitors = ArrayList(*Monitor).init(state.gpa),
            .inputs = ArrayList(*Input).init(state.gpa),
            .globals = undefined,
            .globalsMask = mem.zeroes(GlobalsMask),
        };
    }

    pub fn deinit(self: *Wayland) void {
        for (self.monitors.items) |monitor| monitor.destroy();
        for (self.inputs.items) |input| input.destroy();

        self.monitors.deinit();
        self.inputs.deinit();
    }

    pub fn registerGlobals(self: *Wayland) !void {
        self.registry.setListener(*State, registryListener, self.state);
        _ = try self.display.roundtrip();

        for (self.globalsRegistered) |globalRegistered| {
            if (!globalRegistered) return error.UnsupportedGlobal;
        for (self.globalsMask) |is_registered| {
            if (!is_registered) return error.UnsupportedGlobal;
        }
    }

    fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *State) void {
        const wayland = &state.wayland;
    pub fn getEvent(self: *Wayland) !Event {
        const fd = self.display.getFd();

        return Event{
            .fd = .{
                .fd = @intCast(os.fd_t, fd),
                .events = os.POLL.IN,
                .revents = undefined,
            },
            .data = @ptrCast(*anyopaque, self),
            .callbackIn = dispatch,
            .callbackOut = flush,
        };
    }

    fn dispatch(self_opaque: *anyopaque) error{Terminate}!void {
        const self = utils.cast(Wayland)(self_opaque);
        _ = self.display.dispatch() catch return;
    }

    fn flush(self_opaque: *anyopaque) error{Terminate}!void {
        const self = utils.cast(Wayland)(self_opaque);
        _ = self.display.flush() catch return;
    }

    fn registryListener(
        registry: *wl.Registry,
        event: wl.Registry.Event,
        state: *State,
    ) void {
        const self = &state.wayland;

        switch (event) {
            .global => |global| {
                const interface = global.interface;
                const name = global.name;

                if (strcmp(interface, wl.Compositor.getInterface().name) == 0) {
                    wayland.compositor = registry.bind(name, wl.Compositor, 4) catch return;
                    wayland.globalsRegistered[0] = true;
                } else if (strcmp(interface, wl.Subcompositor.getInterface().name) == 0) {
                    wayland.subcompositor = registry.bind(name, wl.Subcompositor, 1) catch return;
                    wayland.globalsRegistered[1] = true;
                } else if (strcmp(interface, wl.Shm.getInterface().name) == 0) {
                    wayland.shm = registry.bind(name, wl.Shm, 1) catch return;
                    wayland.globalsRegistered[2] = true;
                } else if (strcmp(interface, zwlr.LayerShellV1.getInterface().name) == 0) {
                    wayland.layerShell = registry.bind(name, zwlr.LayerShellV1, 1) catch return;
                    wayland.globalsRegistered[3] = true;
                } else if (strcmp(interface, zriver.StatusManagerV1.getInterface().name) == 0) {
                    wayland.statusManager = registry.bind(name, zriver.StatusManagerV1, 1) catch return;
                    wayland.globalsRegistered[4] = true;
                } else if (strcmp(interface, zriver.ControlV1.getInterface().name) == 0) {
                    wayland.control = registry.bind(name, zriver.ControlV1, 1) catch return;
                    wayland.globalsRegistered[5] = true;
                } else if (strcmp(interface, wl.Output.getInterface().name) == 0) {
                    const output = Output.create(state, registry, name) catch return;
                    wayland.outputs.append(output) catch return;
                } else if (strcmp(interface, wl.Seat.getInterface().name) == 0) {
                    const seat = Seat.create(state, registry, name) catch return;
                    wayland.seats.append(seat) catch return;
                }
            .global => |g| {
                self.bindGlobal(registry, g.interface, g.name) catch return;
            },
            .global_remove => |data| {
                for (wayland.outputs.items) |output, i| {
                    if (output.globalName == data.name) {
                        output.destroy();
                        _ = wayland.outputs.swapRemove(i);
                for (self.monitors.items) |monitor, i| {
                    if (monitor.globalName == data.name) {
                        monitor.destroy();
                        _ = self.monitors.swapRemove(i);
                        break;
                    }
                }
                for (wayland.seats.items) |seat, i| {
                    if (seat.globalName == data.name) {
                        seat.destroy();
                        _ = wayland.seats.swapRemove(i);
                for (self.inputs.items) |input, i| {
                    if (input.globalName == data.name) {
                        input.destroy();
                        _ = self.inputs.swapRemove(i);
                        break;
                    }
                }


@@ 110,12 123,55 @@ pub const Wayland = struct {
        }
    }

    fn bindGlobal(
        self: *Wayland,
        registry: *wl.Registry,
        iface: [*:0]const u8,
        name: u32,
    ) !void {
        if (strcmp(iface, wl.Compositor.getInterface().name) == 0) {
            const global = try registry.bind(name, wl.Compositor, 4);
            self.setGlobal(global);
        } else if (strcmp(iface, wl.Subcompositor.getInterface().name) == 0) {
            const global = try registry.bind(name, wl.Subcompositor, 1);
            self.setGlobal(global);
        } else if (strcmp(iface, wl.Shm.getInterface().name) == 0) {
            const global = try registry.bind(name, wl.Shm, 1);
            self.setGlobal(global);
        } else if (strcmp(iface, zwlr.LayerShellV1.getInterface().name) == 0) {
            const global = try registry.bind(name, zwlr.LayerShellV1, 1);
            self.setGlobal(global);
        } else if (strcmp(iface, zriver.StatusManagerV1.getInterface().name) == 0) {
            const global = try registry.bind(name, zriver.StatusManagerV1, 1);
            self.setGlobal(global);
        } else if (strcmp(iface, zriver.ControlV1.getInterface().name) == 0) {
            const global = try registry.bind(name, zriver.ControlV1, 1);
            self.setGlobal(global);
        } else if (strcmp(iface, wl.Output.getInterface().name) == 0) {
            const monitor = try Monitor.create(self.state, registry, name);
            try self.monitors.append(monitor);
        } else if (strcmp(iface, wl.Seat.getInterface().name) == 0) {
            const input = try Input.create(self.state, registry, name);
            try self.inputs.append(input);
        }
    }

    pub fn setGlobal(self: *Wayland, global: anytype) void {
        inline for (meta.fields(Globals)) |field, i| {
            if (field.field_type == @TypeOf(global)) {
                @field(self.globals, field.name) = global;
                self.globalsMask[i] = true;
                break;
            }
        }
    }

    pub fn findSurface(self: *Wayland, wlSurface: ?*wl.Surface) ?*Surface {
        if (wlSurface == null) {
            return null;
        }
        for (self.outputs.items) |output| {
            if (output.surface) |surface| {
        for (self.monitors.items) |monitor| {
            if (monitor.surface) |surface| {
                if (surface.backgroundSurface == wlSurface or
                    surface.tagsSurface == wlSurface or
                    surface.clockSurface == wlSurface or


@@ 129,58 185,58 @@ pub const Wayland = struct {
    }
};

pub const Output = struct {
pub const Monitor = struct {
    state: *State,
    wlOutput: *wl.Output,
    output: *wl.Output,
    globalName: u32,
    scale: i32,

    surface: ?*Surface,
    tags: *Tags,

    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Output {
        const self = try state.allocator.create(Output);
    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Monitor {
        const self = try state.gpa.create(Monitor);
        self.state = state;
        self.wlOutput = try registry.bind(name, wl.Output, 3);
        self.output = try registry.bind(name, wl.Output, 3);
        self.globalName = name;
        self.scale = 1;

        self.surface = null;
        self.tags = try Tags.create(state, self);

        self.wlOutput.setListener(*Output, listener, self);
        self.output.setListener(*Monitor, listener, self);
        return self;
    }

    pub fn destroy(self: *Output) void {
    pub fn destroy(self: *Monitor) void {
        if (self.surface) |surface| {
            surface.destroy();
        }
        self.tags.destroy();
        self.state.allocator.destroy(self);
        self.state.gpa.destroy(self);
    }

    fn listener(_: *wl.Output, event: wl.Output.Event, output: *Output) void {
    fn listener(_: *wl.Output, event: wl.Output.Event, monitor: *Monitor) void {
        switch (event) {
            .scale => |scale| {
                output.scale = scale.factor;
                monitor.scale = scale.factor;
            },
            .geometry => {},
            .mode => {},
            .name => {},
            .description => {},
            .done => {
                if (output.surface) |_| {} else {
                    output.surface = Surface.create(output) catch return;
                if (monitor.surface) |_| {} else {
                    monitor.surface = Surface.create(monitor) catch return;
                }
            },
        }
    }
};

pub const Seat = struct {
pub const Input = struct {
    state: *State,
    wlSeat: *wl.Seat,
    seat: *wl.Seat,
    globalName: u32,

    pointer: struct {


@@ 190,40 246,40 @@ pub const Seat = struct {
        surface: ?*Surface,
    },

    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Seat {
        const self = try state.allocator.create(Seat);
    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Input {
        const self = try state.gpa.create(Input);
        self.state = state;
        self.wlSeat = try registry.bind(name, wl.Seat, 3);
        self.seat = try registry.bind(name, wl.Seat, 3);
        self.globalName = name;

        self.pointer.wlPointer = null;
        self.pointer.surface = null;

        self.wlSeat.setListener(*Seat, listener, self);
        self.seat.setListener(*Input, listener, self);
        return self;
    }

    pub fn destroy(self: *Seat) void {
    pub fn destroy(self: *Input) void {
        if (self.pointer.wlPointer) |wlPointer| {
            wlPointer.release();
        }
        self.wlSeat.release();
        self.state.allocator.destroy(self);
        self.seat.release();
        self.state.gpa.destroy(self);
    }

    fn listener(wlSeat: *wl.Seat, event: wl.Seat.Event, seat: *Seat) void {
    fn listener(seat: *wl.Seat, event: wl.Seat.Event, input: *Input) void {
        switch (event) {
            .capabilities => |data| {
                if (seat.pointer.wlPointer) |wlPointer| {
                if (input.pointer.wlPointer) |wlPointer| {
                    wlPointer.release();
                    seat.pointer.wlPointer = null;
                    input.pointer.wlPointer = null;
                }
                if (data.capabilities.pointer) {
                    seat.pointer.wlPointer = wlSeat.getPointer() catch return;
                    seat.pointer.wlPointer.?.setListener(
                        *Seat,
                    input.pointer.wlPointer = seat.getPointer() catch return;
                    input.pointer.wlPointer.?.setListener(
                        *Input,
                        pointerListener,
                        seat,
                        input,
                    );
                }
            },


@@ 234,30 290,30 @@ pub const Seat = struct {
    fn pointerListener(
        _: *wl.Pointer,
        event: wl.Pointer.Event,
        seat: *Seat,
        input: *Input,
    ) void {
        switch (event) {
            .enter => |data| {
                seat.pointer.x = data.surface_x.toInt();
                seat.pointer.y = data.surface_y.toInt();
                const surface = seat.state.wayland.findSurface(data.surface);
                seat.pointer.surface = surface;
                input.pointer.x = data.surface_x.toInt();
                input.pointer.y = data.surface_y.toInt();
                const surface = input.state.wayland.findSurface(data.surface);
                input.pointer.surface = surface;
            },
            .leave => |_| {
                seat.pointer.surface = null;
                input.pointer.surface = null;
            },
            .motion => |data| {
                seat.pointer.x = data.surface_x.toInt();
                seat.pointer.y = data.surface_y.toInt();
                input.pointer.x = data.surface_x.toInt();
                input.pointer.y = data.surface_y.toInt();
            },
            .button => |data| {
                if (data.state != .pressed) return;
                if (seat.pointer.surface) |surface| {
                if (input.pointer.surface) |surface| {
                    if (!surface.configured) return;

                    const x = @intCast(u32, seat.pointer.x);
                    const x = @intCast(u32, input.pointer.x);
                    if (x < surface.height * 9) {
                        surface.output.tags.handleClick(x, seat) catch return;
                        surface.monitor.tags.handleClick(x, input) catch return;
                    }
                }
            },


@@ 265,141 321,3 @@ pub const Seat = struct {
        }
    }
};

pub const Surface = struct {
    output: *Output,

    backgroundSurface: *wl.Surface,
    layerSurface: *zwlr.LayerSurfaceV1,
    backgroundBuffers: [2]Buffer,

    tagsSurface: *wl.Surface,
    tagsSubsurface: *wl.Subsurface,
    tagsBuffers: [2]Buffer,

    clockSurface: *wl.Surface,
    clockSubsurface: *wl.Subsurface,
    clockBuffers: [2]Buffer,

    modulesSurface: *wl.Surface,
    modulesSubsurface: *wl.Subsurface,
    modulesBuffers: [2]Buffer,

    configured: bool,
    width: u16,
    height: u16,

    pub fn create(output: *Output) !*Surface {
        const state = output.state;
        const wayland = state.wayland;

        const self = try state.allocator.create(Surface);
        self.output = output;
        self.configured = false;

        self.backgroundSurface = try wayland.compositor.createSurface();
        self.layerSurface = try wayland.layerShell.getLayerSurface(
            self.backgroundSurface,
            output.wlOutput,
            .top,
            "levee",
        );
        self.backgroundBuffers = mem.zeroes([2]Buffer);

        self.tagsSurface = try wayland.compositor.createSurface();
        self.tagsSubsurface = try wayland.subcompositor.getSubsurface(
            self.tagsSurface,
            self.backgroundSurface,
        );
        self.tagsBuffers = mem.zeroes([2]Buffer);

        self.clockSurface = try wayland.compositor.createSurface();
        self.clockSubsurface = try wayland.subcompositor.getSubsurface(
            self.clockSurface,
            self.backgroundSurface,
        );
        self.clockBuffers = mem.zeroes([2]Buffer);

        self.modulesSurface = try wayland.compositor.createSurface();
        self.modulesSubsurface = try wayland.subcompositor.getSubsurface(
            self.modulesSurface,
            self.backgroundSurface,
        );
        self.modulesBuffers = mem.zeroes([2]Buffer);

        // setup layer surface
        self.layerSurface.setSize(0, state.config.height);
        self.layerSurface.setAnchor(
            .{ .top = true, .left = true, .right = true, .bottom = false },
        );
        self.layerSurface.setExclusiveZone(state.config.height);
        self.layerSurface.setMargin(0, 0, 0, 0);
        self.layerSurface.setListener(*Surface, layerSurfaceListener, self);

        // setup subsurfaces
        self.tagsSubsurface.setPosition(0, 0);
        self.clockSubsurface.setPosition(0, 0);
        self.modulesSubsurface.setPosition(0, 0);

        self.tagsSurface.commit();
        self.clockSurface.commit();
        self.backgroundSurface.commit();

        return self;
    }

    pub fn destroy(self: *Surface) void {
        self.output.surface = null;

        self.backgroundSurface.destroy();
        self.layerSurface.destroy();
        self.backgroundBuffers[0].deinit();
        self.backgroundBuffers[1].deinit();

        self.tagsSurface.destroy();
        self.tagsSubsurface.destroy();
        self.tagsBuffers[0].deinit();
        self.tagsBuffers[1].deinit();

        self.clockSurface.destroy();
        self.clockSubsurface.destroy();
        self.clockBuffers[0].deinit();
        self.clockBuffers[1].deinit();

        self.modulesSurface.destroy();
        self.modulesSubsurface.destroy();
        self.modulesBuffers[0].deinit();
        self.modulesBuffers[1].deinit();

        self.output.state.allocator.destroy(self);
    }

    fn layerSurfaceListener(
        layerSurface: *zwlr.LayerSurfaceV1,
        event: zwlr.LayerSurfaceV1.Event,
        surface: *Surface,
    ) void {
        switch (event) {
            .configure => |data| {
                surface.configured = true;
                surface.width = @intCast(u16, data.width);
                surface.height = @intCast(u16, data.height);

                layerSurface.ackConfigure(data.serial);

                render.renderBackground(surface) catch return;
                render.renderTags(surface) catch return;
                render.renderClock(surface) catch return;
                render.renderModules(surface) catch return;

                surface.tagsSurface.commit();
                surface.clockSurface.commit();
                surface.modulesSurface.commit();
                surface.backgroundSurface.commit();
            },
            .closed => {
                surface.destroy();
            },
        }
    }
};