creek

A malleable and minimalist status bar for the River compositor

git clone https://git.8pit.net/creek.git

  1const std = @import("std");
  2const log = std.log;
  3const mem = std.mem;
  4
  5const fcft = @import("fcft");
  6const wl = @import("wayland").client.wl;
  7const wp = @import("wayland").client.wp;
  8const zwlr = @import("wayland").client.zwlr;
  9
 10const Buffer = @import("Buffer.zig");
 11const Monitor = @import("Monitor.zig");
 12const render = @import("render.zig");
 13const Widget = @import("Widget.zig");
 14const Bar = @This();
 15
 16const state = &@import("root").state;
 17
 18monitor: *Monitor,
 19
 20layer_surface: *zwlr.LayerSurfaceV1,
 21background: struct {
 22    surface: *wl.Surface,
 23    viewport: *wp.Viewport,
 24    buffer: *wl.Buffer,
 25},
 26
 27title: Widget,
 28tags: Widget,
 29text: Widget,
 30
 31tags_width: u16,
 32text_width: u16,
 33
 34abbrev_width: u16,
 35abbrev_run: *const fcft.TextRun,
 36
 37text_padding: i32,
 38configured: bool,
 39width: u16,
 40height: u16,
 41
 42// Convert a pixman u16 color to a 32-bit color with a pre-multiplied
 43// alpha channel as used by the "Single-pixel buffer" Wayland protocol.
 44fn toRgba(color: u16) u32 {
 45    return (@as(u32, color) >> 8) << 24 | 0xffffff;
 46}
 47
 48pub fn create(monitor: *Monitor) !*Bar {
 49    const bg_color = &state.config.normalBgColor;
 50    const self = try state.gpa.create(Bar);
 51    self.monitor = monitor;
 52    self.configured = false;
 53
 54    const compositor = state.wayland.compositor.?;
 55    const viewporter = state.wayland.viewporter.?;
 56    const spb_manager = state.wayland.single_pixel_buffer_manager.?;
 57    const layer_shell = state.wayland.layer_shell.?;
 58
 59    self.background.surface = try compositor.createSurface();
 60    self.background.viewport = try viewporter.getViewport(self.background.surface);
 61    self.background.buffer = try spb_manager.createU32RgbaBuffer(toRgba(bg_color.red), toRgba(bg_color.green), toRgba(bg_color.blue), 0xffffffff);
 62
 63    self.layer_surface = try layer_shell.getLayerSurface(self.background.surface, monitor.output, .top, "creek");
 64
 65    self.title = try Widget.init(self.background.surface);
 66    self.tags = try Widget.init(self.background.surface);
 67    self.text = try Widget.init(self.background.surface);
 68
 69    // calculate right padding for status text
 70    const font = state.config.font;
 71    const char_run = try font.rasterizeTextRunUtf32(&[_]u32{' '}, .default);
 72    self.text_padding = char_run.glyphs[0].advance.x;
 73    char_run.destroy();
 74
 75    // rasterize abbreviation glyphs for window ttile.
 76    self.abbrev_run = try font.rasterizeTextRunUtf32(&[_]u32{'…'}, .default);
 77    self.abbrev_width = 0;
 78    var i: usize = 0;
 79    while (i < self.abbrev_run.count) : (i += 1) {
 80        self.abbrev_width += @intCast(self.abbrev_run.glyphs[i].advance.x);
 81    }
 82
 83    // setup layer surface
 84    self.layer_surface.setSize(0, state.config.height);
 85    self.layer_surface.setAnchor(
 86        .{ .top = true, .left = true, .right = true, .bottom = false },
 87    );
 88    self.layer_surface.setExclusiveZone(state.config.height);
 89    self.layer_surface.setMargin(0, 0, 0, 0);
 90    self.layer_surface.setListener(*Bar, layerSurfaceListener, self);
 91
 92    self.tags.surface.commit();
 93    self.title.surface.commit();
 94    self.text.surface.commit();
 95    self.background.surface.commit();
 96
 97    self.tags_width = 0;
 98    self.text_width = 0;
 99
100    return self;
101}
102
103pub fn destroy(self: *Bar) void {
104    self.abbrev_run.destroy();
105    self.monitor.bar = null;
106
107    self.layer_surface.destroy();
108
109    self.background.buffer.destroy();
110    self.background.viewport.destroy();
111    self.background.surface.destroy();
112
113    self.title.deinit();
114    self.tags.deinit();
115    self.text.deinit();
116    state.gpa.destroy(self);
117}
118
119fn layerSurfaceListener(
120    layerSurface: *zwlr.LayerSurfaceV1,
121    event: zwlr.LayerSurfaceV1.Event,
122    bar: *Bar,
123) void {
124    switch (event) {
125        .configure => |data| {
126            layerSurface.ackConfigure(data.serial);
127
128            const w: u16 = @intCast(data.width);
129            const h: u16 = @intCast(data.height);
130            if (bar.configured and bar.width == w and bar.height == h) {
131                return;
132            }
133
134            bar.configured = true;
135            bar.width = w;
136            bar.height = h;
137
138            const bg = &bar.background;
139            bg.surface.attach(bg.buffer, 0, 0);
140            bg.surface.damageBuffer(0, 0, bar.width, bar.height);
141            bg.viewport.setDestination(bar.width, bar.height);
142
143            render.renderTags(bar) catch |err| {
144                log.err("renderTags failed for monitor {}: {s}", .{ bar.monitor.globalName, @errorName(err) });
145                return;
146            };
147
148            bar.tags.surface.commit();
149            bar.title.surface.commit();
150            bar.text.surface.commit();
151            bar.background.surface.commit();
152        },
153        .closed => bar.destroy(),
154    }
155}