~novakane/zelbar

81ad34693a1d46da882b18e5dfcd48a2cb5dedf6 — Hugo Machet 1 year, 6 months ago 299b2b3
Refactor

Lot of things changed (yes it should have been made in multiple commit):
  * Removed scale for now, it was really buggy
  * Bar: Move everything that was related to wayland to Backend.zig.
  * Backend: Move the creation, configuration and rendering of the
    surface in Surface.send_frame().
  * Add some doc comments
  * Cleanup
5 files changed, 150 insertions(+), 159 deletions(-)

M src/Backend.zig
M src/Bar.zig
M src/decorations.zig
M src/event_loop.zig
M src/main.zig
M src/Backend.zig => src/Backend.zig +127 -119
@@ 37,7 37,7 @@ const Output = struct {
    wl_name: u32,

    name: ?[]const u8 = null,
    scale: i32 = 1,
    // TODO: scale

    configured: bool = false,



@@ 50,9 50,13 @@ const Output = struct {
        output.wl_output.setListener(*Output, output_listener, output);
    }

    fn deinit(output: *Output) void {
    fn destroy(output: *Output) void {
        output.wl_output.release();
        if (output.name) |name| ctx.gpa.free(name);

        const node = @fieldParentPtr(std.SinglyLinkedList(Output).Node, "data", output);
        ctx.backend.outputs.remove(node);
        ctx.gpa.destroy(node);
    }

    fn output_listener(_: *wl.Output, event: wl.Output.Event, output: *Output) void {


@@ 61,7 65,6 @@ const Output = struct {
                log.info("output {?s} configured", .{output.name});
                output.configured = true;
            },
            .scale => |ev| output.scale = ev.factor,
            .name => |ev| {
                output.name = ctx.gpa.dupe(u8, mem.span(ev.name)) catch |err| switch (err) {
                    error.OutOfMemory => {


@@ 125,9 128,13 @@ const Seat = struct {
        seat.wl_seat.setListener(*Seat, seat_listener, seat);
    }

    fn deinit(seat: *Seat) void {
    fn destroy(seat: *Seat) void {
        seat.release_pointer();
        seat.wl_seat.release();

        const node = @fieldParentPtr(std.SinglyLinkedList(Seat).Node, "data", seat);
        ctx.backend.seats.remove(node);
        ctx.gpa.destroy(node);
    }

    fn seat_listener(_: *wl.Seat, event: wl.Seat.Event, seat: *Seat) void {


@@ 222,7 229,7 @@ const Seat = struct {
            .hand => "pointer",
        };

        const cursor_size = 24 * ctx.backend.scale;
        const cursor_size = 24;

        if (seat.cursor_theme == null) {
            seat.cursor_theme = try wl.CursorTheme.load(null, cursor_size, ctx.backend.shm.?);


@@ 245,7 252,6 @@ const Seat = struct {
            seat.cursor_surface = null;
        }

        seat.cursor_surface.?.setBufferScale(ctx.backend.scale);
        seat.cursor_surface.?.attach(wl_buffer, 0, 0);
        seat.cursor_surface.?.damageBuffer(0, 0, std.math.maxInt(i31), std.math.maxInt(u31));
        seat.cursor_surface.?.commit();


@@ 253,8 259,8 @@ const Seat = struct {
        seat.wl_pointer.?.setCursor(
            seat.last_enter_serial,
            seat.cursor_surface.?,
            @divFloor(@intCast(i32, cursor_image.hotspot_x), ctx.backend.scale),
            @divFloor(@intCast(i32, cursor_image.hotspot_y), ctx.backend.scale),
            @intCast(i32, cursor_image.hotspot_x),
            @intCast(i32, cursor_image.hotspot_y),
        );
    }
};


@@ 271,43 277,91 @@ const Surface = struct {
    // True if we need to redraw.
    dirty: bool = false,

    width: u31 = 0,
    height: u31 = 0,

    configured: bool = false,

    fn init(surface: *Surface) !void {
        const wl_surface = try ctx.backend.compositor.?.createSurface();
        const layer_surface = try ctx.backend.layer_shell.?.getLayerSurface(
            wl_surface,
            ctx.backend.req_output.wl_output,
            switch (ctx.bar.config.layer) {
                .bottom => .bottom,
                .top => .top,
                .overlay => .overlay,
            },
            "panel",
        );
    fn deinit(surface: *Surface) void {
        surface.configured = false;
        surface.hotspots.deinit(ctx.gpa);
        if (surface.frame_callback) |cb| cb.destroy();
        surface.layer_surface.destroy();
        surface.wl_surface.destroy();
    }

        surface.* = .{
            .wl_surface = wl_surface,
            .layer_surface = layer_surface,
        };
    fn send_frame(surface: *Surface) !void {
        // The surface is not initialized yet.
        if (!surface.configured) {
            const wl_surface = try ctx.backend.compositor.?.createSurface();
            const layer_surface = try ctx.backend.layer_shell.?.getLayerSurface(
                wl_surface,
                ctx.backend.req_output.wl_output,
                switch (ctx.bar.config.layer) {
                    .bottom => .bottom,
                    .top => .top,
                    .overlay => .overlay,
                },
                "panel",
            );

            surface.* = .{
                .wl_surface = wl_surface,
                .layer_surface = layer_surface,
            };
            surface.layer_surface.setListener(*Surface, layer_surface_listener, surface);
        }

        // Configure the layer_surface.
        surface.layer_surface.setListener(*Surface, layer_surface_listener, surface);
        configure_layer(surface.layer_surface, 0, ctx.bar.config.height, ctx.backend.scale);
        // Configure the layer_surface and resize it if needed.
        if (surface.height != ctx.bar.config.height) {
            const cfg = ctx.bar.config;
            surface.layer_surface.setSize(0, cfg.height);
            if (cfg.layer == .overlay) {
                surface.layer_surface.setExclusiveZone(0);
            } else {
                surface.layer_surface.setExclusiveZone(cfg.height);
            }
            switch (cfg.location) {
                .top => {
                    surface.layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = false, .left = true });
                },
                .bottom => {
                    surface.layer_surface.setAnchor(.{ .top = false, .right = true, .bottom = true, .left = true });
                },
            }
            surface.layer_surface.setMargin(cfg.margins.top, cfg.margins.right, cfg.margins.bottom, cfg.margins.left);

        // We need to commit the empty surface first so we can receive a configure
        // event with width and height requested for our surface.
        surface.wl_surface.commit();
            // We need to commit the empty surface first so we can receive a configure
            // event with width and height requested for our surface.
            surface.wl_surface.commit();

            return;
        }

        // If we made it till here we can finally draw on your surface.
        // Get a new buffer not busy.
        const buffer = try ctx.backend.pool.next_buffer(surface.width, surface.height);
        assert(!buffer.busy);

        try ctx.bar.render(buffer, @intCast(u16, surface.width), @intCast(u16, surface.height));

        // Attach the buffer to the surface.
        surface.wl_surface.attach(buffer.wl_buffer, 0, 0);
        surface.wl_surface.damageBuffer(0, 0, buffer.width, buffer.height);
        buffer.busy = true;

        // Schedule a frame in case the surface become dirty again.
        surface.schedule_frame_and_commit();
        surface.dirty = false;
    }

    fn deinit(surface: *Surface) void {
        if (surface.frame_callback) |cb| cb.destroy();
        surface.hotspots.deinit(ctx.gpa);
        surface.layer_surface.destroy();
        surface.wl_surface.destroy();
    pub fn set_dirty(surface: *Surface) void {
        if (surface.dirty) return;
        surface.dirty = true;
        surface.schedule_frame_and_commit();
    }

    pub fn schedule_frame_and_commit(surface: *Surface) void {
    fn schedule_frame_and_commit(surface: *Surface) void {
        if (surface.frame_pending) return;

        // Request a new callback for the next frame.


@@ 317,12 371,6 @@ const Surface = struct {
        surface.frame_pending = true;
    }

    pub fn set_dirty(surface: *Surface) void {
        if (surface.dirty) return;
        surface.dirty = true;
        surface.schedule_frame_and_commit();
    }

    fn hotspot_from_point(surface: *Surface, x: u31, y: u31) ?*HotSpot {
        for (surface.hotspots.items) |*hs| {
            if (hs.contains_point(x, y)) return hs;


@@ 330,34 378,6 @@ const Surface = struct {
        return null;
    }

    fn configure_layer(
        layer_surface: *zwlr.LayerSurfaceV1,
        width: u31,
        height: u31,
        scale: i32,
    ) void {
        layer_surface.setSize(width, height);
        if (ctx.bar.config.layer == .overlay) {
            layer_surface.setExclusiveZone(0);
        } else {
            layer_surface.setExclusiveZone(height * scale);
        }
        switch (ctx.bar.config.location) {
            .top => {
                layer_surface.setAnchor(.{ .top = true, .right = true, .bottom = false, .left = true });
            },
            .bottom => {
                layer_surface.setAnchor(.{ .top = false, .right = true, .bottom = true, .left = true });
            },
        }
        layer_surface.setMargin(
            ctx.bar.config.margins.top * scale,
            ctx.bar.config.margins.right * scale,
            ctx.bar.config.margins.bottom * scale,
            ctx.bar.config.margins.left * scale,
        );
    }

    fn layer_surface_listener(
        _: *zwlr.LayerSurfaceV1,
        event: zwlr.LayerSurfaceV1.Event,


@@ 365,19 385,21 @@ const Surface = struct {
    ) void {
        switch (event) {
            .configure => |ev| {
                surface.configured = true;
                surface.layer_surface.ackConfigure(ev.serial);

                const w = @truncate(u31, ev.width);
                const h = @truncate(u31, ev.height);

                if (surface.configured and ctx.backend.width != w or ctx.backend.height != h) {
                    configure_layer(surface.layer_surface, w, h, ctx.backend.scale);

                    ctx.backend.width = w * @intCast(u31, ctx.backend.scale);
                    ctx.backend.height = h * @intCast(u31, ctx.backend.scale);
                    ctx.bar.render() catch return;
                if (surface.configured and surface.width == w and surface.height == h) {
                    surface.wl_surface.commit();
                    return;
                }

                surface.configured = true;
                surface.width = w;
                surface.height = h;

                surface.send_frame() catch return;
            },
            .closed => {
                surface.deinit();


@@ 395,13 417,13 @@ const Surface = struct {
                surface.frame_callback = null;
                surface.frame_pending = false;

                if (surface.dirty) ctx.bar.render() catch return;
                if (surface.dirty) surface.send_frame() catch return;
            },
        }
    }
};

// TODO: Credit source when published
// Taken and adapted from https://git.sr.ht/~leon_plickat/wayprompt, same license.
const BufferPool = struct {
    /// The amount of buffers per surface we consider the reasonable upper limit.
    /// Some compositors sometimes tripple-buffer, so three seems to be ok.


@@ 493,7 515,7 @@ const BufferPool = struct {
    }
};

const Buffer = struct {
pub const Buffer = struct {
    wl_buffer: ?*wl.Buffer = null,
    data: ?[]align(std.mem.page_size) u8 = null,
    pixman_image: ?*pixman.Image = null,


@@ 575,24 597,21 @@ shm: ?*wl.Shm = null,
layer_shell: ?*zwlr.LayerShellV1 = null,

outputs: std.SinglyLinkedList(Output) = .{},
req_output: *Output = undefined,
scale: i32 = 1,

seats: std.SinglyLinkedList(Seat) = .{},

/// Requested output by the user to display the bar on.
// TODO: What if the req_output is destroyed
req_output: *Output = undefined,
surface: ?Surface = null,

pool: BufferPool = .{},

width: u31 = 0,
height: u31 = 0,

pub fn init(backend: *Backend, cfg: Bar.Config) !bool {
pub fn init(backend: *Backend) !bool {
    backend.* = .{
        .display = wl.Display.connect(null) catch {
            ctx.fatal(.backend, "failed to connect to Wayland compositor", .{});
        },
        .registry = try backend.display.getRegistry(),
        .surface = Surface{},
    };

    backend.registry.setListener(*Backend, registry_listener, backend);


@@ 610,7 629,7 @@ pub fn init(backend: *Backend, cfg: Bar.Config) !bool {
    _ = backend.display.roundtrip();

    // Get the output requested by the user or use the first one in the outputs list.
    if (cfg.output_name) |name| {
    if (ctx.bar.config.output_name) |name| {
        var it = backend.outputs.first;
        while (it) |node| : (it = node.next) {
            const output_name = node.data.name orelse continue;


@@ 623,19 642,12 @@ pub fn init(backend: *Backend, cfg: Bar.Config) !bool {
        }
    } else {
        const node = backend.outputs.first orelse return error.NoOutputDetected;
        log.info(
            "no output requested, using the first in the list({s})",
            .{node.data.name.?},
        );
        log.info("no output requested, using the first in the list({?s})", .{node.data.name});
        backend.req_output = &node.data;
    }
    backend.scale = backend.req_output.scale;

    ctx.bar = try Bar.new(cfg);

    // Create the surface after the bar so we have all infos needed from the bar config.
    backend.surface = Surface{};
    try backend.surface.?.init();
    // We finally have all the infos needed to create the surface and start rendering.
    try backend.surface.?.send_frame();

    return backend.display.roundtrip() == .SUCCESS;
}


@@ 652,14 664,8 @@ pub fn deinit(backend: *Backend) void {
    if (backend.shm) |shm| shm.destroy();
    if (backend.layer_shell) |ls| ls.destroy();

    while (backend.outputs.popFirst()) |node| {
        node.data.deinit();
        ctx.gpa.destroy(node);
    }
    while (backend.seats.popFirst()) |node| {
        node.data.deinit();
        ctx.gpa.destroy(node);
    }
    while (backend.outputs.first) |node| node.data.destroy();
    while (backend.seats.first) |node| node.data.destroy();

    backend.registry.destroy();
    backend.display.disconnect();


@@ 764,21 770,23 @@ fn registry_event(backend: *Backend, registry: *wl.Registry, event: wl.Registry.
            }
        },
        .global_remove => |ev| {
            var output_it = backend.outputs.first;
            while (output_it) |node| : (output_it = node.next) {
                if (node.data.wl_name == ev.name) {
                    node.data.deinit();
                    backend.outputs.remove(node);
                    break;
            {
                var it = backend.outputs.first;
                while (it) |node| : (it = node.next) {
                    if (node.data.wl_name == ev.name) {
                        node.data.destroy();
                        break;
                    }
                }
            }

            var seat_it = backend.seats.first;
            while (seat_it) |node| : (seat_it = node.next) {
                if (node.data.id == ev.name) {
                    node.data.deinit();
                    backend.seats.remove(node);
                    break;
            {
                var it = backend.seats.first;
                while (it) |node| : (it = node.next) {
                    if (node.data.id == ev.name) {
                        node.data.destroy();
                        break;
                    }
                }
            }
        },

M src/Bar.zig => src/Bar.zig +17 -38
@@ 20,6 20,7 @@ const assert = std.debug.assert;
const fcft = @import("fcft");

const ctx = &@import("main.zig").context;
const Buffer = @import("Backend.zig").Buffer;
const data = @import("data.zig");
const decorations = @import("decorations.zig");
const Text = @import("Text.zig");


@@ 40,6 41,7 @@ pub const Config = struct {
    width: u16 = 0,
    height: u16 = 26,

    /// Margins around the Surface.
    margins: struct { top: u16, left: u16, bottom: u16, right: u16 } = .{
        .top = 0,
        .left = 0,


@@ 48,9 50,7 @@ pub const Config = struct {
    },

    colors: std.AutoHashMapUnmanaged(enum { bg, fg, line }, []const u8) = .{},

    border: decorations.Border = .{},

    line_height: u16 = 2,

    fonts_name: []const u8 = "sans",


@@ 59,13 59,13 @@ pub const Config = struct {
config: Config = .{},
font: *fcft.Font = undefined,

/// Raw input received from STDIN.
input: std.ArrayListUnmanaged(u8) = .{},

/// True if user requested a width (i.e. config.width > 0). If false the width
/// received in the layer_surface configure event is used.
req_width: bool = false,

/// Raw input received from STDIN.
input: std.ArrayListUnmanaged(u8) = .{},

pub fn new(config: Config) !Bar {
    return Bar{
        .config = config,


@@ 80,52 80,31 @@ pub fn deinit(bar: *Bar) void {
    bar.input.deinit(ctx.gpa);
}

pub fn render(bar: *Bar) !void {
    const w = if (bar.req_width) bar.config.width else @intCast(u16, ctx.backend.width);
/// Draw the bar on the Buffer.
/// surface_w is always the width of the output.
pub fn render(bar: *Bar, buffer: *Buffer, surface_w: u16, h: u16) !void {
    // Width where we draw our bar.
    const w = if (bar.req_width) bar.config.width else surface_w;

    bar.config.border.check_size(w, bar.config.height) catch |err| switch (err) {
        error.BordersGreaterHeight => {
            ctx.fatal(.bar, "borders greater than bar height({d})", .{bar.config.height});
        },
        error.BordersGreaterWidth => {
            ctx.fatal(.bar, "borders greater than bar width({d})", .{w});
        },
    bar.config.border.check_size(w, h) catch |err| switch (err) {
        error.BordersGreaterHeight => ctx.fatal(.bar, "borders greater than bar height({d})", .{h}),
        error.BordersGreaterWidth => ctx.fatal(.bar, "borders greater than bar width({d})", .{w}),
        else => return err,
    };

    // Starting position of the bar in the output.
    const x: u16 = switch (bar.config.alignment) {
        .left => 0,
        .center => @intCast(u16, ctx.backend.width >> 1) - (bar.config.width >> 1),
        .right => @intCast(u16, ctx.backend.width) - bar.config.width,
        .center => surface_w / 2 - (w >> 1),
        .right => surface_w - w,
    };

    // Get a new buffer not busy.
    const buffer = try ctx.backend.pool.next_buffer(w, ctx.backend.height);
    assert(!buffer.busy);

    // Render the background and the borders of the bar.
    const pix = buffer.pixman_image orelse return error.PixmanImageEmpty;
    const bg = bar.config.colors.get(.bg).?;
    try decorations.draw_rectangle(pix, @intCast(i16, x), 0, w, bar.config.height, bg);
    try bar.config.border.draw(pix, @intCast(i16, x), 0, w, bar.config.height);
    try decorations.draw_rectangle(pix, @intCast(i16, x), 0, w, h, bg);
    try bar.config.border.draw(pix, @intCast(i16, x), 0, w, h);

    // Parse and render the raw input if any.
    if (bar.input.items.len > 0) try data.expose(bar.input.items[0..], pix, x, w);

    // Attach the buffer to the surface.
    if (ctx.backend.surface) |*surface| {
        if (!surface.configured) {
            log.warn("surface is not configured", .{});
            return;
        }
        surface.wl_surface.setBufferScale(ctx.backend.scale);
        surface.wl_surface.attach(buffer.wl_buffer, 0, 0);
        surface.wl_surface.damageBuffer(0, 0, buffer.width, buffer.height);
        buffer.busy = true;

        // Schedule a frame in case the surface become dirty again.
        surface.schedule_frame_and_commit();
        surface.dirty = false;
    }
}

M src/decorations.zig => src/decorations.zig +3 -0
@@ 98,6 98,8 @@ pub const Border = struct {
        _ = pixman.Image.fillRectangles(.src, image, &c, 4, &borders);
    }

    /// Check if the size of the borders would not be greater than the one
    /// of the bar.
    pub fn check_size(border: *Border, width: u31, height: u31) !void {
        if (border.top > height or border.bottom > height or
            border.top + border.bottom > height)


@@ 139,6 141,7 @@ pub fn parse_rgba(s: []const u8) !pixman.Color {
    };
}

/// Check if the color use the right format: "0xRRGGBB" or "0xRRGGBBAA".
pub fn check_rgba(comptime scope: @Type(.EnumLiteral), s: []const u8) void {
    if (s.len != 8 and s.len != 10) {
        ctx.fatal(scope, "color must be '0xRRGGBB' or '0xRRGGBBAA': {s}", .{s});

M src/event_loop.zig => src/event_loop.zig +1 -1
@@ 66,8 66,8 @@ pub fn run() !void {
            continue;
        } else if (pollfds[poll_stdin].revents & os.POLL.IN != 0) {
            var buf: [4096]u8 = undefined;

            const read = try os.read(os.STDIN_FILENO, buf[0..]);
            if (read == 0) break;

            if (!mem.eql(u8, buf[0..read], ctx.bar.input.items[0..])) {
                log.debug("read {d} bytes: {s}", .{ read, buf[0..read] });

M src/main.zig => src/main.zig +2 -1
@@ 203,7 203,8 @@ pub fn main() anyerror!void {
    }
    defer fcft.fini();

    context.initialized = try context.backend.init(default_cfg);
    context.bar = try Bar.new(default_cfg);
    context.initialized = try context.backend.init();
    defer context.deinit();

    try event_loop.run();