~andreafeletto/levee

e0ba69e3fef33bbfbfd5207ad1c1546fb25cebc5 — Andrea Feletto 4 months ago a773bcb
add clock
8 files changed, 155 insertions(+), 77 deletions(-)

M .gitmodules
M build.zig
A deps/zig-datetime
M deps/zig-fcft
A src/clock.zig
M src/main.zig
M src/tags.zig
M src/wayland.zig
M .gitmodules => .gitmodules +3 -0
@@ 7,3 7,6 @@
[submodule "deps/zig-fcft"]
	path = deps/zig-fcft
	url = https://git.sr.ht/~andreafeletto/zig-fcft
[submodule "deps/zig-datetime"]
	path = deps/zig-datetime
	url = https://github.com/frmdstryr/zig-datetime

M build.zig => build.zig +8 -2
@@ 13,15 13,19 @@ pub fn build(b: *std.build.Builder) void {
    scanner.addProtocolPath("protocol/river-status-unstable-v1.xml");

    const wayland = scanner.getPkg();
    const pixman = Pkg {
    const pixman = Pkg{
        .name = "pixman",
        .path = "deps/zig-pixman/pixman.zig",
    };
    const fcft = Pkg {
    const fcft = Pkg{
        .name = "fcft",
        .path = "deps/zig-fcft/fcft.zig",
        .dependencies = &[_]Pkg{ pixman },
    };
    const datetime = Pkg{
        .name = "datetime",
        .path = "deps/zig-datetime/src/main.zig",
    };

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


@@ 40,6 44,8 @@ pub fn build(b: *std.build.Builder) void {
    exe.addPackage(fcft);
    exe.linkSystemLibrary("fcft");

    exe.addPackage(datetime);

    exe.install();

    const run_cmd = exe.run();

A deps/zig-datetime => deps/zig-datetime +1 -0
@@ 0,0 1,1 @@
Subproject commit d5deebbe44079a22ccf4cc856a3424160ffab1c2

M deps/zig-fcft => deps/zig-fcft +1 -1
@@ 1,1 1,1 @@
Subproject commit 3cf9bd470c8106b4ca0c90439de4851e29067b02
Subproject commit 182fcc99f770ce6a8fe3080fa8ca21eecef3085f

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

const datetime = @import("datetime").datetime;
const pixman = @import("pixman");
const fcft = @import("fcft");

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

pub const Clock = struct {
    thread: ?*std.Thread = null,
    stop: bool = false,

    pub fn render(self: *const Clock, pix: *pixman.Image, state: *State) !void {
        const width: i16 = 100;
        const height = @intCast(i16, state.wayland.height);
        const offset = @divFloor(@intCast(i16, state.wayland.width) - width, 2);

        var now = datetime.Datetime.now();
        const str = try now.formatHttp(state.allocator);
        std.log.info("{s}", .{ str });

        // const outer = [_]pixman.Rectangle16{
        //     .{ .x = offset, .y = 0, .width = width, .height = height },
        // };
        // _ = pixman.Image.fillRectangles(.over, pix, state.foreground, 1, &outer);

        var x = offset;
        for (str) |char, i| {
            var color = pixman.Image.createSolidFill(state.foreground).?;
            const glyph = try fcft.Glyph.rasterize(state.font, char, .default);
            // x += @intCast(i16, glyph.advance.x);
            x += 14;
            //const y = @divFloor(height - @intCast(i16, glyph.height), 2);
            const y = 10;
            std.log.info("{d}, {d}", .{ x, y });
            pixman.Image.composite32(
                .over, color, glyph.pix, pix,
                0, 0, 0, 0, x, y,
                glyph.width, glyph.height
            );
        }
    }

    pub fn start(self: *Clock, state: *State) !void {
        self.thread = try std.Thread.spawn(repeatDraw, state);
    }
};

fn repeatDraw(state: *State) void {
    while (!state.clock.stop) {
        state.draw(state);
        std.time.sleep(1 * std.time.ns_per_s);
    }
}

M src/main.zig => src/main.zig +21 -12
@@ 8,10 8,12 @@ const fcft = @import("fcft");
const Wayland = @import("wayland.zig").Wayland;
const Buffer = @import("shm.zig").Buffer;
const Tags = @import("tags.zig").Tags;
const Clock = @import("clock.zig").Clock;

pub const State = struct {
    wayland: *Wayland,
    tags: *Tags,
    clock: *Clock,
    allocator: *mem.Allocator,
    font: *fcft.Font,
    foreground: *const pixman.Color,


@@ 20,13 22,17 @@ pub const State = struct {
};

pub fn main() anyerror!void {
    var wayland = try Wayland.init();
    var tags = try Tags.init(&wayland);

    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = &arena.allocator;

    const wayland = try Wayland.create(allocator);
    defer wayland.destroy();

    var tags = try Tags.init(wayland);
    var clock = Clock{};

    var font_names = [_][*:0]const u8{ "monospace:size=18" };
    var font_names = [_][*:0]const u8{ "monospace:size=14" };
    const foreground = pixman.Color{
        .red = 0xffff,
        .green = 0xffff,


@@ 41,17 47,20 @@ pub fn main() anyerror!void {
    };

    var state = State{
        .wayland = &wayland,
        .wayland = wayland,
        .tags = &tags,
        .allocator = &arena.allocator,
        .font = fcft.Font.fromName(mem.span(&font_names), null).?,
        .clock = &clock,
        .allocator = allocator,
        .font = try fcft.Font.fromName(font_names[0..], null),

        .foreground = &foreground,
        .background = &background,
        .draw = draw,
    };

    try wayland.setup(30);
    try wayland.setup(34);
    try tags.listen(&state);
    try clock.start(&state);
    draw(&state);
    wayland.loop();
}


@@ 59,10 68,11 @@ pub fn main() anyerror!void {
fn draw(state: *State) void {
    const width = @intCast(i32, state.wayland.width);
    const height = @intCast(i32, state.wayland.height);
    const surface = state.wayland.wlSurface.?;
    const surface = state.wayland.wlSurface;

    const shm = state.wayland.shm.?;
    const buffer = Buffer.create(state.allocator, shm, width, height) catch return;
    defer buffer.destroy();

    const bg = pixman.Color{ .red = 0, .green = 0, .blue = 0, .alpha = 0xffff };
    const rects = [_]pixman.Rectangle16{


@@ 70,11 80,10 @@ fn draw(state: *State) void {
    };
    _ = pixman.Image.fillRectangles(.src, buffer.pix, &bg, 1, &rects);

    state.tags.render(buffer.pix, state);
    state.tags.render(buffer.pix, state) catch return;
    state.clock.render(buffer.pix, state) catch return;

    surface.attach(buffer.wlBuffer, 0, 0);
    surface.damageBuffer(0, 0, width, height);
    surface.commit();

    buffer.destroy();
}

M src/tags.zig => src/tags.zig +4 -4
@@ 16,7 16,7 @@ pub const Tag = struct {
    focused: bool = false,
    occupied: bool = false,

    pub fn render(self: *const Tag, pix: *pixman.Image, offset: i16, state: *State) void {
    pub fn render(self: *const Tag, pix: *pixman.Image, offset: i16, state: *State) !void {
        const size = @intCast(u16, state.wayland.height);

        const outer = [_]pixman.Rectangle16{


@@ 40,7 40,7 @@ pub const Tag = struct {
        else
            state.foreground;
        var char = pixman.Image.createSolidFill(glyph_color).?;
        const glyph = fcft.Glyph.rasterize(state.font, self.label, .default).?;
        const glyph = try fcft.Glyph.rasterize(state.font, self.label, .default);
        const x = offset + @divFloor(size - glyph.width, 2);
        const y = @divFloor(size - glyph.height, 2);
        pixman.Image.composite32(


@@ 87,10 87,10 @@ pub const Tags = struct {
        _ = try state.wayland.display.roundtrip();
    }

    pub fn render(self: *const Tags, pix: *pixman.Image, state: *State) void {
    pub fn render(self: *const Tags, pix: *pixman.Image, state: *State) !void {
        for (self.tags) |tag, i| {
            const offset = @intCast(i16, state.wayland.height * i);
            tag.render(pix, offset, state);
            try tag.render(pix, offset, state);
        }
    }


M src/wayland.zig => src/wayland.zig +62 -58
@@ 8,7 8,13 @@ const zwlr = wayland.zwlr;

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

pub const WaylandError = error {
    GlobalsUnavailable,
};

pub const Wayland = struct {
    allocator: *mem.Allocator,

    display: *wl.Display,
    registry: *wl.Registry,



@@ 17,53 23,34 @@ pub const Wayland = struct {
    output: ?*wl.Output = null,
    layerShell: ?*zwlr.LayerShellV1 = null,

    wlSurface: ?*wl.Surface = null,
    layerSurface: ?*zwlr.LayerSurfaceV1 = null,
    wlSurface: *wl.Surface,
    layerSurface: *zwlr.LayerSurfaceV1,

    running: bool = false,
    width: u32 = 0,
    height: u32 = 0,
    running: bool = true,

    pub fn init() !Wayland {
        const display = try wl.Display.connect(null);
        const registry = try display.getRegistry();
        var result = Wayland{
            .display = display,
            .registry = registry,
        };
        registry.setListener(*Wayland, globalListener, &result);
        _ = try display.roundtrip();

        if (result.shm == null) {
            std.log.err("globals: shared memory not available", .{});
            os.exit(1);
        }
        if (result.compositor == null) {
            std.log.err("globals: compositor not available", .{});
            os.exit(1);
        }
        if (result.output == null) {
            std.log.err("globals: output not available", .{});
            os.exit(1);
        }
        if (result.layerShell == null) {
            std.log.err("globals: layer shell not available", .{});
            os.exit(1);
        }

        result.wlSurface = try result.compositor.?.createSurface();
        result.layerSurface = try result.layerShell.?.getLayerSurface(
            result.wlSurface.?,
            result.output.?,
            .overlay,
            "wlroots"
        );
    pub fn create(allocator: *mem.Allocator) !*Wayland {
        const result = try allocator.create(Wayland);
        errdefer allocator.destroy(result);
        result.allocator = allocator;

        result.display = try wl.Display.connect(null);
        errdefer result.display.disconnect();

        return result;
        result.registry = try result.display.getRegistry();
        result.registry.setListener(*Wayland, globalListener, result);

        result.running = false;
        const sync_callback = try result.display.sync();
        sync_callback.setListener(*Wayland, syncListener, result);

        _ = try result.display.roundtrip();
        return if (result.running) result else error.GlobalsUnavailable;
    }

    pub fn setup(self: *Wayland, height: u32) !void {
        const surface = self.layerSurface.?;
        const surface = self.layerSurface;

        surface.setSize(0, height);
        surface.setAnchor(.{ .top = true, .left = true, .right = true, .bottom = false });


@@ 71,13 58,7 @@ pub const Wayland = struct {
        surface.setMargin(0, 0, 0, 0);
        surface.setListener(*Wayland, layerSurfaceListener, self);

        self.wlSurface.?.commit();
        _ = try self.display.roundtrip();
    }

    pub fn frame(self: *Wayland, state: *State) !void {
        const cb = try self.wlSurface.?.frame();
        cb.setListener(*State, frameListener, state);
        self.wlSurface.commit();
        _ = try self.display.roundtrip();
    }



@@ 87,8 68,9 @@ pub const Wayland = struct {
        }
    }

    pub fn deinit(self: *Wayland) void {
    pub fn destroy(self: *Wayland) void {
        self.display.disconnect();
        self.allocator.destroy(self);
    }
};



@@ 112,6 94,38 @@ fn globalListener(registry: *wl.Registry, event: wl.Registry.Event, state: *Wayl
    }
}

fn syncListener(callback: *wl.Callback, event: wl.Callback.Event, state: *Wayland) void {
    switch (event) {
        .done => {
            if (state.shm == null) {
                std.log.err("globals: shared memory not available", .{});
                return;
            }
            if (state.compositor == null) {
                std.log.err("globals: compositor not available", .{});
                return;
            }
            if (state.output == null) {
                std.log.err("globals: output not available", .{});
                return;
            }
            if (state.layerShell == null) {
                std.log.err("globals: layer shell not available", .{});
                return;
            }

            state.wlSurface = state.compositor.?.createSurface() catch return;

            const shell = state.layerShell.?;
            const output = state.output.?;
            const surface = state.wlSurface;
            state.layerSurface = shell.getLayerSurface(surface, output, .overlay, "wlroots") catch return;

            state.running = true;
        },
    }
}

fn layerSurfaceListener(surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV1.Event, state: *Wayland) void {
    switch (event) {
        .configure => |data| {


@@ 121,18 135,8 @@ fn layerSurfaceListener(surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV
        },
        .closed => {
            surface.destroy();
            state.wlSurface.?.destroy();
            state.wlSurface.destroy();
            state.running = false;
        },
    }
}

fn frameListener(callback: *wl.Callback, event: wl.Callback.Event, state: *State) void {
    switch (event) {
        .done => |time| {
            const cb = state.wayland.wlSurface.?.frame() catch return;
            cb.setListener(*State, frameListener, state);
            state.draw(state);
        }
    }
}