~novakane/zelbar

a51fea97156624abaa1ec56b6407f1ebaab7df08 — Hugo Machet 9 months ago 3e5a597
Render only in response to frame callbacks
4 files changed, 73 insertions(+), 37 deletions(-)

M src/Backend.zig
M src/Bar.zig
M src/data.zig
M src/event_loop.zig
M src/Backend.zig => src/Backend.zig +37 -2
@@ 274,6 274,12 @@ const Surface = struct {

    hotspots: std.ArrayListUnmanaged(HotSpot) = .{},

    frame_callback: ?*wl.Callback = null,
    /// True if we requested a frame callback.
    frame_pending: bool = false,
    // True if we need to redraw.
    dirty: bool = false,

    configured: bool = false,

    fn init(surface: *Surface) !void {


@@ 304,11 310,28 @@ const Surface = struct {
    }

    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 schedule_frame_and_commit(surface: *Surface) void {
        if (surface.frame_pending) return;

        // Request a new callback for the next frame.
        surface.frame_callback = surface.wl_surface.frame() catch return;
        surface.frame_callback.?.setListener(*Surface, frame_callback_listener, surface);
        surface.wl_surface.commit();
        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;


@@ 362,8 385,7 @@ const Surface = struct {

                    ctx.backend.width = w * @intCast(u31, ctx.backend.scale);
                    ctx.backend.height = h * @intCast(u31, ctx.backend.scale);

                    ctx.bar.render("") catch return;
                    ctx.bar.render() catch return;
                }
            },
            .closed => {


@@ 373,6 395,19 @@ const Surface = struct {
            },
        }
    }

    fn frame_callback_listener(_: *wl.Callback, event: wl.Callback.Event, surface: *Surface) void {
        switch (event) {
            .done => {
                // Destroy the now used callback.
                if (surface.frame_callback) |cb| cb.destroy();
                surface.frame_callback = null;
                surface.frame_pending = false;

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

// TODO: Credit source when published

M src/Bar.zig => src/Bar.zig +25 -19
@@ 15,9 15,9 @@
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

const std = @import("std");
const assert = std.debug.assert;

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

const ctx = &@import("main.zig").context;
const data = @import("data.zig");


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

image: ?*pixman.Image = null,
/// 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.


@@ 76,13 77,10 @@ pub fn new(config: Config) !Bar {
pub fn deinit(bar: *Bar) void {
    bar.font.destroy();
    bar.config.colors.deinit(ctx.gpa);
    bar.input.deinit(ctx.gpa);
}

pub fn render(bar: *Bar, input: []const u8) !void {
    const buffer = try ctx.backend.pool.next_buffer(ctx.backend.width, ctx.backend.height);
    std.debug.assert(!buffer.busy);
    ctx.bar.image = buffer.pixman_image;

pub fn render(bar: *Bar) !void {
    const w = if (bar.req_width) bar.config.width else @intCast(u16, ctx.backend.width);

    bar.config.border.check_size(w, bar.config.height) catch |err| switch (err) {


@@ 102,6 100,10 @@ pub fn render(bar: *Bar, input: []const u8) !void {
        .right => @intCast(u16, ctx.backend.width) - bar.config.width,
    };

    // 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).?;


@@ 109,17 111,21 @@ pub fn render(bar: *Bar, input: []const u8) !void {
    try bar.config.border.draw(pix, @intCast(i16, x), 0, w, bar.config.height);

    // Parse and render the raw input if any.
    try data.expose(input, x, w);

    const surface = ctx.backend.surface.?;
    if (!surface.configured) {
        log.warn("surface is not configured", .{});
        return;
    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;
    }
    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);
    surface.wl_surface.commit();

    buffer.busy = true;
}

M src/data.zig => src/data.zig +7 -8
@@ 19,6 19,8 @@ const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;

const pixman = @import("pixman");

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


@@ 27,9 29,7 @@ const Tokenizer = @import("Tokenizer.zig");
const log = std.log.scoped(.entry);

/// Parse and render entries data.
pub fn expose(input: []const u8, bar_x: u16, bar_w: u16) !void {
    if (input.len == 0) return;

pub fn expose(input: []const u8, pix: *pixman.Image, bar_x: u16, bar_w: u16) !void {
    // Height an Entry can use.
    const entry_h = blk: {
        if (ctx.bar.config.border.bottom + ctx.bar.config.border.top >= ctx.bar.config.height) {


@@ 61,9 61,9 @@ pub fn expose(input: []const u8, bar_x: u16, bar_w: u16) !void {

        var e = entries.list.get(index);
        switch (e.alignment) {
            .left => entries_x[L] -= try e.render(@ptrCast(*i16, &entries_x[L]), entry_h),
            .center => entries_x[C] -= try e.render(@ptrCast(*i16, &entries_x[C]), entry_h),
            .right => entries_x[R] -= try e.render(@ptrCast(*i16, &entries_x[R]), entry_h),
            .left => entries_x[L] -= try e.render(pix, @ptrCast(*i16, &entries_x[L]), entry_h),
            .center => entries_x[C] -= try e.render(pix, @ptrCast(*i16, &entries_x[C]), entry_h),
            .right => entries_x[R] -= try e.render(pix, @ptrCast(*i16, &entries_x[R]), entry_h),
        }
    }



@@ 229,7 229,7 @@ const Entry = struct {
    ///   height: Usable entry height based on bar height.
    ///
    /// Return the total width of the entry to use as 'x' for the next one.
    pub fn render(entry: Entry, prev_x: *i16, height: u16) !u16 {
    pub fn render(entry: Entry, pix: *pixman.Image, prev_x: *i16, height: u16) !u16 {
        var x = prev_x.*;

        switch (entry.alignment) {


@@ 243,7 243,6 @@ const Entry = struct {
        );

        const bg = entry.colors.get(.bg) orelse ctx.bar.config.colors.get(.bg).?;
        const pix = ctx.bar.image orelse return error.PixmanImageEmpty;
        try decorations.draw_rectangle(pix, x, y, entry.width, height, bg);

        if (entry.action) |action| {

M src/event_loop.zig => src/event_loop.zig +4 -8
@@ 53,9 53,6 @@ pub fn run() !void {
        .flags = 0,
    }, null);

    var old_input: std.ArrayListUnmanaged(u8) = .{};
    defer old_input.deinit(ctx.gpa);

    while (ctx.initialized) {
        ctx.backend.flush_wayland_and_prepare_read();



@@ 72,13 69,12 @@ pub fn run() !void {

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

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

                old_input.clearRetainingCapacity();
                try old_input.appendSlice(ctx.gpa, buf[0..read]);

                try ctx.bar.render(buf[0..read]);
                ctx.bar.input.clearRetainingCapacity();
                try ctx.bar.input.appendSlice(ctx.gpa, buf[0..read]);
                ctx.backend.surface.?.set_dirty();
            }
        }