~andreafeletto/levee

a185435ed45757f71a2aee94cc95a464f5b0d798 — Andrea Feletto 3 months ago 3d7537a
add river tags
3 files changed, 220 insertions(+), 66 deletions(-)

M src/main.zig
A src/tags.zig
M src/wayland.zig
M src/main.zig => src/main.zig +32 -16
@@ 7,40 7,62 @@ const fcft = @import("fcft");

const Wayland = @import("wayland.zig").Wayland;
const Buffer = @import("shm.zig").Buffer;
const Tags = @import("tags.zig").Tags;

pub const State = struct {
    wayland: Wayland,
    wayland: *Wayland,
    tags: *Tags,
    allocator: *mem.Allocator,
    font: *fcft.Font,
    foreground: *const pixman.Color,
    background: *const pixman.Color,
    draw: fn (state: *State) void,
};

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

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

    var state = State{
        .wayland = try Wayland.init(),
        .wayland = &wayland,
        .tags = &tags,
        .allocator = &arena.allocator,
        .font = fcft.Font.fromName(mem.span(&font_names), null).?,
        .foreground = &foreground,
        .background = &background,
        .draw = draw,
    };

    state.wayland.prepare();
    try state.wayland.setup(30);
    try state.wayland.frame(&state);
    try wayland.setup(30);
    try tags.listen(&state);
    draw(&state);
    state.wayland.loop();
    wayland.loop();
}

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 buffer = Buffer.create(state.allocator, state.wayland.shm, width, height) catch return;
    const shm = state.wayland.shm.?;
    const buffer = Buffer.create(state.allocator, shm, width, height) catch return;

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


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

    const fg = pixman.Color{ .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
    var charfg = pixman.Image.createSolidFill(&fg).?;
    const y = @divFloor(height - 3 * state.font.height, 2);
    renderChar('A', buffer, 0, charfg, state);
    state.tags.render(buffer.pix, state);

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

fn renderChar(char: u8, buffer: *Buffer, y: c_int, color: *pixman.Image, state: *State) void {
    const glyph = fcft.Glyph.rasterize(state.font, char, .default).?;
    pixman.Image.composite32(.over, color, glyph.pix, buffer.pix, 0, 0, 0, 0, 0, 0, glyph.width, glyph.height);
    buffer.destroy();
}

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

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

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

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

pub const Tag = struct {
    label: u8,
    focused: bool = false,
    occupied: bool = false,

    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{
            .{ .x = offset, .y = 0, .width = size, .height = size },
        };
        const outer_color = if (self.focused or self.occupied)
            state.foreground
        else
            state.background;
        _ = pixman.Image.fillRectangles(.over, pix, outer_color, 1, &outer);

        const inner = [_]pixman.Rectangle16{
            .{ .x = offset + 1, .y = 1, .width = size - 2, .height = size - 2 },
        };
        if (!self.focused and self.occupied) {
            _ = pixman.Image.fillRectangles(.over, pix, state.background, 1, &inner);
        }

        const glyph_color = if (self.focused)
            state.background
        else
            state.foreground;
        var char = pixman.Image.createSolidFill(glyph_color).?;
        const glyph = 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(
            .over, char, glyph.pix, pix,
            0, 0, 0, 0, x, y,
            glyph.width, glyph.height
        );
    }
};

pub const Tags = struct {
    statusManager: ?*zriver.StatusManagerV1 = null,
    outputStatus: *zriver.OutputStatusV1,
    tags: [10]Tag,

    pub fn init(state: *Wayland) !Tags {
        var result = Tags{
            .outputStatus = undefined,
            .tags = std.mem.zeroes([10]Tag),
        };

        for (result.tags) |*tag, i| {
            tag.label = '1' + @intCast(u8, i);
        }

        const registry = try state.display.getRegistry();
        registry.setListener(*Tags, globalListener, &result);
        _ = try state.display.roundtrip();

        if (result.statusManager == null) {
            std.log.err("globals: river status manager not available", .{});
            os.exit(1);
        }

        result.outputStatus = try result.statusManager.?.getRiverOutputStatus(
            state.output.?,
        );

        return result;
    }

    pub fn listen(self: *Tags, state: *State) !void {
        self.outputStatus.setListener(*State, outputStatusListener, state);
        _ = try state.wayland.display.roundtrip();
    }

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

    pub fn resetOccupied(self: *Tags) void {
        for (self.tags) |*tag| {
            tag.occupied = false;
        }
    }

    pub fn deinit(self: *Tags) void {
        self.outputStatus.destroy();
        self.statusManager.?.destroy();
    }
};

fn globalListener(registry: *wl.Registry, event: wl.Registry.Event, state: *Tags) void {
    switch (event) {
        .global => |global| {
            const inter = zriver.StatusManagerV1;
            const name = inter.getInterface().name;
            if (std.cstr.cmp(global.interface, name) == 0) {
                state.statusManager = registry.bind(global.name, inter, 1) catch return;
            }
        },
        .global_remove => {},
    }
}

fn outputStatusListener(status: *zriver.OutputStatusV1, event: zriver.OutputStatusV1.Event, state: *State) void {
    switch (event) {
        .focused_tags => |data| {
            for (state.tags.tags) |*tag, i| {
                const mask = @as(u32, 1) << @intCast(u5, i);
                tag.focused = data.tags & mask != 0;
            }
        },
        .view_tags => |data| {
            state.tags.resetOccupied();
            for (data.tags.slice(u32)) |view| {
                for (state.tags.tags) |*tag, i| {
                    const mask = @as(u32, 1) << @intCast(u5, i);
                    if (view & mask != 0) tag.occupied = true;
                }
            }
        },
    }
    state.draw(state);
}

M src/wayland.zig => src/wayland.zig +47 -50
@@ 1,5 1,6 @@
const std = @import("std");
const mem = std.mem;
const os = std.os;

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


@@ 11,13 12,13 @@ pub const Wayland = struct {
    display: *wl.Display,
    registry: *wl.Registry,

    shm: *wl.Shm,
    compositor: *wl.Compositor,
    output: *wl.Output,
    layerShell: *zwlr.LayerShellV1,
    shm: ?*wl.Shm = null,
    compositor: ?*wl.Compositor = null,
    output: ?*wl.Output = null,
    layerShell: ?*zwlr.LayerShellV1 = null,

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

    width: u32 = 0,
    height: u32 = 0,


@@ 25,54 26,57 @@ pub const Wayland = struct {

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

        var globals = Globals{};
        const registry = try display.getRegistry();
        registry.setListener(*Globals, globalListener, &globals);
        _ = try display.roundtrip();

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

        const compositor = globals.compositor.?;
        const wlSurface = try compositor.createSurface();

        const layerShell = globals.layerShell.?;
        const output = globals.output.?;
        const layerSurface = try layerShell.getLayerSurface(wlSurface, output, .overlay, "wlroots");

        return Wayland{
        var result = Wayland{
            .display = display,
            .registry = registry,
            .shm = globals.shm.?,
            .compositor = compositor,
            .output = globals.output.?,
            .layerShell = layerShell,
            .wlSurface = wlSurface,
            .layerSurface = layerSurface,
        };
    }
        registry.setListener(*Wayland, globalListener, &result);
        _ = try display.roundtrip();

    pub fn prepare(self: *Wayland) void {
        self.layerSurface.setListener(*Wayland, layerSurfaceListener, self);
        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"
        );

        return result;
    }

    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 });
        surface.setExclusiveZone(@intCast(i32, height));
        surface.setMargin(0, 0, 0, 0);
        surface.setListener(*Wayland, layerSurfaceListener, self);

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

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


@@ 88,27 92,20 @@ pub const Wayland = struct {
    }
};

const Globals = struct {
    shm: ?*wl.Shm = null,
    compositor: ?*wl.Compositor = null,
    output: ?*wl.Output = null,
    layerShell: ?*zwlr.LayerShellV1 = null,
};

fn globalListener(registry: *wl.Registry, event: wl.Registry.Event, globals: *Globals) void {
fn globalListener(registry: *wl.Registry, event: wl.Registry.Event, state: *Wayland) void {
    switch (event) {
        .global => |global| {
            const cmp = std.cstr.cmp;
            const interface = global.interface;

            if (cmp(interface, wl.Compositor.getInterface().name) == 0) {
                globals.compositor = registry.bind(global.name, wl.Compositor, 4) catch return;
                state.compositor = registry.bind(global.name, wl.Compositor, 4) catch return;
            } else if (cmp(interface, wl.Shm.getInterface().name) == 0) {
                globals.shm = registry.bind(global.name, wl.Shm, 1) catch return;
                state.shm = registry.bind(global.name, wl.Shm, 1) catch return;
            } else if (cmp(interface, wl.Output.getInterface().name) == 0) {
                globals.output = registry.bind(global.name, wl.Output, 1) catch return;
                state.output = registry.bind(global.name, wl.Output, 1) catch return;
            } else if (cmp(interface, zwlr.LayerShellV1.getInterface().name) == 0) {
                globals.layerShell = registry.bind(global.name, zwlr.LayerShellV1, 1) catch return;
                state.layerShell = registry.bind(global.name, zwlr.LayerShellV1, 1) catch return;
            }
        },
        .global_remove => {},


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


@@ 133,7 130,7 @@ fn layerSurfaceListener(surface: *zwlr.LayerSurfaceV1, event: zwlr.LayerSurfaceV
fn frameListener(callback: *wl.Callback, event: wl.Callback.Event, state: *State) void {
    switch (event) {
        .done => |time| {
            const cb = state.wayland.wlSurface.frame() catch return;
            const cb = state.wayland.wlSurface.?.frame() catch return;
            cb.setListener(*State, frameListener, state);
            state.draw(state);
        }