~andreafeletto/levee

4934bf6a9050f492e5ae8133eb0efb160e55835d — Andrea Feletto a month ago 27e73da
shrink subsurface buffers
13 files changed, 326 insertions(+), 353 deletions(-)

A src/Bar.zig
R src/{config.zig => Config.zig}
M src/Loop.zig
D src/Surface.zig
M src/Tags.zig
M src/main.zig
M src/modules.zig
M src/modules/Alsa.zig
M src/modules/Backlight.zig
M src/modules/Battery.zig
M src/render.zig
M src/utils.zig
M src/wayland.zig
A src/Bar.zig => src/Bar.zig +135 -0
@@ 0,0 1,135 @@
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 State = @import("main.zig").State;
const Bar = @This();

monitor: *Monitor,

layerSurface: *zwlr.LayerSurfaceV1,
background: struct {
    surface: *wl.Surface,
    buffer: Buffer,
},

tags: Widget,
clock: Widget,
modules: Widget,

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

pub const Widget = struct {
    surface: *wl.Surface,
    subsurface: *wl.Subsurface,
    buffers: [2]Buffer,

    pub fn init(state: *State, background: *wl.Surface) !Widget {
        const globals = &state.wayland.globals;

        const surface = try globals.compositor.createSurface();
        const subsurface = try globals.subcompositor.getSubsurface(
            surface,
            background,
        );

        return Widget{
            .surface = surface,
            .subsurface = subsurface,
            .buffers = mem.zeroes([2]Buffer),
        };
    }

    pub fn deinit(self: *Widget) void {
        self.surface.destroy();
        self.subsurface.destroy();
        self.buffers[0].deinit();
        self.buffers[1].deinit();
    }
};

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

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

    self.background.surface = try globals.compositor.createSurface();
    self.layerSurface = try globals.layerShell.getLayerSurface(
        self.background.surface,
        monitor.output,
        .top,
        "levee",
    );
    self.background.buffer = mem.zeroes(Buffer);

    self.tags = try Widget.init(state, self.background.surface);
    self.clock = try Widget.init(state, self.background.surface);
    self.modules = try Widget.init(state, self.background.surface);

    // 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(*Bar, layerSurfaceListener, self);

    self.tags.surface.commit();
    self.clock.surface.commit();
    self.modules.surface.commit();
    self.background.surface.commit();

    return self;
}

pub fn destroy(self: *Bar) void {
    self.monitor.bar = null;

    self.background.surface.destroy();
    self.layerSurface.destroy();
    self.background.buffer.deinit();

    self.tags.deinit();
    self.clock.deinit();
    self.modules.deinit();

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

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

            layerSurface.ackConfigure(data.serial);

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

            bar.tags.surface.commit();
            bar.clock.surface.commit();
            bar.modules.surface.commit();
            bar.background.surface.commit();
        },
        .closed => bar.destroy(),
    }
}

R src/config.zig => src/Config.zig +29 -29
@@ 1,34 1,34 @@
const fcft = @import("fcft");
const pixman = @import("pixman");

pub const Config = struct {
    height: u16,
    backgroundColor: pixman.Color,
    foregroundColor: pixman.Color,
    border: u15,
    font: *fcft.Font,
    clockFormat: [*:0]const u8,
const Config = @This();

    pub fn init() !Config {
        var font_names = [_][*:0]const u8{"monospace:size=14"};
height: u16,
backgroundColor: pixman.Color,
foregroundColor: pixman.Color,
border: u15,
font: *fcft.Font,
clockFormat: [*:0]const u8,

        return Config{
            .height = 32,
            .backgroundColor = .{
                .red = 0,
                .green = 0,
                .blue = 0,
                .alpha = 0xffff,
            },
            .foregroundColor = .{
                .red = 0xffff,
                .green = 0xffff,
                .blue = 0xffff,
                .alpha = 0xffff,
            },
            .border = 2,
            .font = try fcft.Font.fromName(&font_names, null),
            .clockFormat = "%d %b %Y - %R",
        };
    }
};
pub fn init() !Config {
    var font_names = [_][*:0]const u8{"monospace:size=14"};

    return Config{
        .height = 32,
        .backgroundColor = .{
            .red = 0,
            .green = 0,
            .blue = 0,
            .alpha = 0xffff,
        },
        .foregroundColor = .{
            .red = 0xffff,
            .green = 0xffff,
            .blue = 0xffff,
            .alpha = 0xffff,
        },
        .border = 2,
        .font = try fcft.Font.fromName(&font_names, null),
        .clockFormat = "%d %b %Y - %R",
    };
}

M src/Loop.zig => src/Loop.zig +0 -5
@@ 1,13 1,8 @@
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,

D src/Surface.zig => src/Surface.zig +0 -146
@@ 1,146 0,0 @@
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();
        },
    }
}

M src/Tags.zig => src/Tags.zig +7 -7
@@ 63,11 63,11 @@ fn outputStatusListener(
            }
        },
    }
    if (tags.monitor.surface) |surface| {
        if (surface.configured) {
            render.renderTags(surface) catch return;
            surface.tagsSurface.commit();
            surface.backgroundSurface.commit();
    if (tags.monitor.bar) |bar| {
        if (bar.configured) {
            render.renderTags(bar) catch return;
            bar.tags.surface.commit();
            bar.background.surface.commit();
        }
    }
}


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

    if (self.monitor.surface) |surface| {
        const index = x / surface.height;
    if (self.monitor.bar) |bar| {
        const index = x / bar.height;
        const payload = try std.fmt.allocPrintZ(
            state.gpa,
            "{d}",

M src/main.zig => src/main.zig +1 -1
@@ 4,7 4,7 @@ const mem = std.mem;

const fcft = @import("fcft");

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

M src/modules.zig => src/modules.zig +0 -1
@@ 1,5 1,4 @@
const std = @import("std");
const os = std.os;

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


M src/modules/Alsa.zig => src/modules/Alsa.zig +7 -7
@@ 101,13 101,13 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    _ = 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();
        if (monitor.bar) |bar| {
            if (bar.configured) {
                render.renderClock(bar) catch continue;
                render.renderModules(bar) catch continue;
                bar.clock.surface.commit();
                bar.modules.surface.commit();
                bar.background.surface.commit();
            }
        }
    }

M src/modules/Backlight.zig => src/modules/Backlight.zig +5 -5
@@ 85,11 85,11 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {

    _ = 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();
        if (monitor.bar) |bar| {
            if (bar.configured) {
                render.renderModules(bar) catch continue;
                bar.modules.surface.commit();
                bar.background.surface.commit();
            }
        }
    }

M src/modules/Battery.zig => src/modules/Battery.zig +7 -7
@@ 93,13 93,13 @@ fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void {
    _ = 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();
        if (monitor.bar) |bar| {
            if (bar.configured) {
                render.renderClock(bar) catch continue;
                render.renderModules(bar) catch continue;
                bar.clock.surface.commit();
                bar.modules.surface.commit();
                bar.background.surface.commit();
            }
        }
    }

M src/render.zig => src/render.zig +93 -121
@@ 7,136 7,114 @@ const time = @cImport(@cInclude("time.h"));

const Buffer = @import("Buffer.zig");
const State = @import("main.zig").State;
const Surface = @import("Surface.zig");
const Bar = @import("Bar.zig");
const Tag = @import("Tags.zig").Tag;
const utils = @import("utils.zig");

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

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

    const buffer = try Buffer.nextBuffer(
        &surface.backgroundBuffers,
        state.wayland.globals.shm,
        surface.width,
        surface.height,
    );
    buffer.busy = true;
    const buffer = &bar.background.buffer;
    if (buffer.width == bar.width and buffer.height == bar.height) return;
    try buffer.init(state.wayland.globals.shm, bar.width, bar.height);

    const area = [_]pixman.Rectangle16{
        .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
        .{ .x = 0, .y = 0, .width = bar.width, .height = bar.height },
    };
    const color = &state.config.backgroundColor;
    _ = pixman.Image.fillRectangles(.src, buffer.pix.?, color, 1, &area);

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

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

    const width = bar.height * 9;
    const buffer = try Buffer.nextBuffer(
        &surface.tagsBuffers,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
        &bar.tags.buffers,
        bar.monitor.state.wayland.globals.shm,
        width,
        bar.height,
    );
    buffer.busy = true;

    for (tags) |*tag, i| {
        const offset = @intCast(i16, surface.height * i);
        try renderTag(buffer.pix.?, tag, surface.height, offset, state);
        const offset = @intCast(i16, bar.height * i);
        try renderTag(buffer.pix.?, tag, bar.height, offset, state);
    }

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

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

    const buffer = try Buffer.nextBuffer(
        &surface.clockBuffers,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
    );
    buffer.busy = true;
pub fn renderClock(bar: *Bar) !void {
    const state = bar.monitor.state;
    const surface = bar.clock.surface;
    const shm = state.wayland.globals.shm;

    // clear the buffer
    const bg_area = [_]pixman.Rectangle16{
        .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
    };
    const bg_color = mem.zeroes(pixman.Color);
    _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);

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

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

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

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

    const run = try fcft.TextRun.rasterizeUtf32(
        state.config.font,
        runes,
        .default,
    );
    // resterize
    const font = state.config.font;
    const run = try fcft.TextRun.rasterizeUtf32(font, runes, .default);
    defer run.destroy();

    i = 0;
    var text_width: u32 = 0;
    // compute total width
    var i: usize = 0;
    var width: u16 = 0;
    while (i < run.count) : (i += 1) {
        text_width += @intCast(u32, run.glyphs[i].advance.x);
        width += @intCast(u16, run.glyphs[i].advance.x);
    }

    const font_height = @intCast(u32, state.config.font.height);
    var x_offset = @intCast(i32, @divFloor(surface.width - text_width, 2));
    var y_offset = @intCast(i32, @divFloor(surface.height - font_height, 2));
    // set subsurface offset
    const font_height = @intCast(u32, font.height);
    const x_offset = @intCast(i32, (bar.width - width) / 2);
    const y_offset = @intCast(i32, (bar.height - font_height) / 2);
    bar.clock.subsurface.setPosition(x_offset, y_offset);

    const buffers = &bar.clock.buffers;
    const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
    buffer.busy = true;

    const bg_area = [_]pixman.Rectangle16{
        .{ .x = 0, .y = 0, .width = width, .height = bar.height },
    };
    const bg_color = mem.zeroes(pixman.Color);
    _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);

    var x: i32 = 0;
    i = 0;
    var color = pixman.Image.createSolidFill(&state.config.foregroundColor).?;
    while (i < run.count) : (i += 1) {
        const glyph = run.glyphs[i];
        const x = x_offset + @intCast(i32, glyph.x);
        const y = y_offset + state.config.font.ascent - @intCast(i32, glyph.y);
        x += @intCast(i32, glyph.x);
        const y = state.config.font.ascent - @intCast(i32, glyph.y);
        pixman.Image.composite32(.over, color, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
        x_offset += glyph.advance.x;
        x += glyph.advance.x - @intCast(i32, glyph.x);
    }

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

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

    const buffer = try Buffer.nextBuffer(
        &surface.modulesBuffers,
        surface.monitor.state.wayland.globals.shm,
        surface.width,
        surface.height,
    );
    buffer.busy = true;
pub fn renderModules(bar: *Bar) !void {
    const state = bar.monitor.state;
    const surface = bar.modules.surface;
    const shm = state.wayland.globals.shm;

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


@@ 149,57 127,51 @@ pub fn renderModules(surface: *Surface) !void {
    }

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

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

    // rasterize
    const font = state.config.font;
    const run = try fcft.TextRun.rasterizeUtf32(font, runes, .default);
    defer run.destroy();

    // compute total width
    var i: usize = 0;
    while (utf8_iter.nextCodepoint()) |rune| : (i += 1) {
        runes[i] = rune;
    var width: u16 = 0;
    while (i < run.count) : (i += 1) {
        width += @intCast(u16, run.glyphs[i].advance.x);
    }
    runes = state.gpa.resize(runes, i).?;

    // clear the buffer
    // set subsurface offset
    const font_height = @intCast(u32, state.config.font.height);
    var x_offset = @intCast(i32, bar.width - width);
    var y_offset = @intCast(i32, @divFloor(bar.height - font_height, 2));
    bar.modules.subsurface.setPosition(x_offset, y_offset);

    const buffers = &bar.modules.buffers;
    const buffer = try Buffer.nextBuffer(buffers, shm, width, bar.height);
    buffer.busy = true;

    const bg_area = [_]pixman.Rectangle16{
        .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
        .{ .x = 0, .y = 0, .width = width, .height = bar.height },
    };
    const bg_color = mem.zeroes(pixman.Color);
    _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);

    // compute offsets
    const run = try fcft.TextRun.rasterizeUtf32(
        state.config.font,
        runes,
        .default,
    );
    defer run.destroy();

    i = 0;
    var text_width: u32 = 0;
    while (i < run.count) : (i += 1) {
        text_width += @intCast(u32, run.glyphs[i].advance.x);
    }

    const font_height = @intCast(u32, state.config.font.height);
    var x_offset = @intCast(i32, surface.width - text_width);
    var y_offset = @intCast(i32, @divFloor(surface.height - font_height, 2));

    // resterize
    var x: i32 = 0;
    i = 0;
    var color = pixman.Image.createSolidFill(&state.config.foregroundColor).?;
    while (i < run.count) : (i += 1) {
        const glyph = run.glyphs[i];
        const x = x_offset + @intCast(i32, glyph.x);
        const y = y_offset + state.config.font.ascent - @intCast(i32, glyph.y);
        x += @intCast(i32, glyph.x);
        const y = state.config.font.ascent - @intCast(i32, glyph.y);
        pixman.Image.composite32(.over, color, glyph.pix, buffer.pix.?, 0, 0, 0, 0, x, y, glyph.width, glyph.height);
        x_offset += glyph.advance.x;
        x += glyph.advance.x - @intCast(i32, glyph.x);
    }

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

fn renderTag(

M src/utils.zig => src/utils.zig +16 -0
@@ 1,5 1,7 @@
const std = @import("std");
const mem = std.mem;
const meta = std.meta;
const unicode = std.unicode;

pub fn cast(comptime to: type) fn (*anyopaque) *to {
    return (struct {


@@ 13,3 15,17 @@ pub fn Mask(comptime container: type) type {
    const len = meta.fields(container).len;
    return [len]bool;
}

pub fn toUtf8(gpa: mem.Allocator, bytes: []const u8) ![]u32 {
    const utf8 = try unicode.Utf8View.init(bytes);
    var iter = utf8.iterator();

    var runes = try gpa.alloc(u32, bytes.len);
    var i: usize = 0;
    while (iter.nextCodepoint()) |rune| : (i += 1) {
        runes[i] = rune;
    }

    runes = gpa.resize(runes, i).?;
    return runes;
}

M src/wayland.zig => src/wayland.zig +26 -24
@@ 13,7 13,7 @@ const Buffer = @import("Buffer.zig");
const Event = @import("Loop.zig").Event;
const render = @import("render.zig");
const State = @import("main.zig").State;
const Surface = @import("Surface.zig");
const Bar = @import("Bar.zig");
const Tags = @import("Tags.zig");
const utils = @import("utils.zig");



@@ 141,7 141,9 @@ pub const Wayland = struct {
        } 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) {
        } 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) {


@@ 166,18 168,18 @@ pub const Wayland = struct {
        }
    }

    pub fn findSurface(self: *Wayland, wlSurface: ?*wl.Surface) ?*Surface {
    pub fn findBar(self: *Wayland, wlSurface: ?*wl.Surface) ?*Bar {
        if (wlSurface == null) {
            return null;
        }
        for (self.monitors.items) |monitor| {
            if (monitor.surface) |surface| {
                if (surface.backgroundSurface == wlSurface or
                    surface.tagsSurface == wlSurface or
                    surface.clockSurface == wlSurface or
                    surface.modulesSurface == wlSurface)
            if (monitor.bar) |bar| {
                if (bar.background.surface == wlSurface or
                    bar.tags.surface == wlSurface or
                    bar.clock.surface == wlSurface or
                    bar.modules.surface == wlSurface)
                {
                    return surface;
                    return bar;
                }
            }
        }


@@ 191,7 193,7 @@ pub const Monitor = struct {
    globalName: u32,
    scale: i32,

    surface: ?*Surface,
    bar: ?*Bar,
    tags: *Tags,

    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Monitor {


@@ 201,7 203,7 @@ pub const Monitor = struct {
        self.globalName = name;
        self.scale = 1;

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

        self.output.setListener(*Monitor, listener, self);


@@ 209,8 211,8 @@ pub const Monitor = struct {
    }

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


@@ 226,8 228,8 @@ pub const Monitor = struct {
            .name => {},
            .description => {},
            .done => {
                if (monitor.surface) |_| {} else {
                    monitor.surface = Surface.create(monitor) catch return;
                if (monitor.bar) |_| {} else {
                    monitor.bar = Bar.create(monitor) catch return;
                }
            },
        }


@@ 243,7 245,7 @@ pub const Input = struct {
        wlPointer: ?*wl.Pointer,
        x: i32,
        y: i32,
        surface: ?*Surface,
        bar: ?*Bar,
    },

    pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Input {


@@ 253,7 255,7 @@ pub const Input = struct {
        self.globalName = name;

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

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


@@ 296,11 298,11 @@ pub const Input = struct {
            .enter => |data| {
                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;
                const bar = input.state.wayland.findBar(data.surface);
                input.pointer.bar = bar;
            },
            .leave => |_| {
                input.pointer.surface = null;
                input.pointer.bar = null;
            },
            .motion => |data| {
                input.pointer.x = data.surface_x.toInt();


@@ 308,12 310,12 @@ pub const Input = struct {
            },
            .button => |data| {
                if (data.state != .pressed) return;
                if (input.pointer.surface) |surface| {
                    if (!surface.configured) return;
                if (input.pointer.bar) |bar| {
                    if (!bar.configured) return;

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