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;
  3
  4const zriver = @import("wayland").client.zriver;
  5const pixman = @import("pixman");
  6
  7const Monitor = @import("Monitor.zig");
  8const render = @import("render.zig");
  9const Input = @import("Input.zig");
 10const Tags = @This();
 11
 12const state = &@import("root").state;
 13
 14monitor: *Monitor,
 15output_status: *zriver.OutputStatusV1,
 16tags: [9]Tag,
 17
 18pub const Tag = struct {
 19    label: u8,
 20    focused: bool = false,
 21    occupied: bool = false,
 22    urgent: bool = false,
 23
 24    pub fn bgColor(self: *const Tag) *pixman.Color {
 25        if (self.focused) {
 26            return &state.config.focusBgColor;
 27        } else if (self.urgent) {
 28            return &state.config.normalFgColor;
 29        } else {
 30            return &state.config.normalBgColor;
 31        }
 32    }
 33
 34    pub fn fgColor(self: *const Tag) *pixman.Color {
 35        if (self.focused) {
 36            return &state.config.focusFgColor;
 37        } else if (self.urgent) {
 38            return &state.config.normalBgColor;
 39        } else {
 40            return &state.config.normalFgColor;
 41        }
 42    }
 43};
 44
 45pub fn create(monitor: *Monitor) !*Tags {
 46    const self = try state.gpa.create(Tags);
 47    const manager = state.wayland.status_manager.?;
 48
 49    self.monitor = monitor;
 50    self.output_status = try manager.getRiverOutputStatus(monitor.output);
 51    for (&self.tags, 0..) |*tag, i| {
 52        tag.label = '1' + @as(u8, @intCast(i));
 53    }
 54
 55    self.output_status.setListener(*Tags, outputStatusListener, self);
 56    return self;
 57}
 58
 59pub fn destroy(self: *Tags) void {
 60    self.output_status.destroy();
 61    state.gpa.destroy(self);
 62}
 63
 64fn outputStatusListener(
 65    _: *zriver.OutputStatusV1,
 66    event: zriver.OutputStatusV1.Event,
 67    tags: *Tags,
 68) void {
 69    switch (event) {
 70        .focused_tags => |data| {
 71            for (&tags.tags, 0..) |*tag, i| {
 72                const mask = @as(u32, 1) << @as(u5, @intCast(i));
 73                tag.focused = data.tags & mask != 0;
 74            }
 75        },
 76        .urgent_tags => |data| {
 77            for (&tags.tags, 0..) |*tag, i| {
 78                const mask = @as(u32, 1) << @as(u5, @intCast(i));
 79                tag.urgent = data.tags & mask != 0;
 80            }
 81        },
 82        .view_tags => |data| {
 83            for (&tags.tags) |*tag| {
 84                tag.occupied = false;
 85            }
 86            for (data.tags.slice(u32)) |view| {
 87                for (&tags.tags, 0..) |*tag, i| {
 88                    const mask = @as(u32, 1) << @as(u5, @intCast(i));
 89                    if (view & mask != 0) tag.occupied = true;
 90                }
 91            }
 92        },
 93    }
 94    if (tags.monitor.confBar()) |bar| {
 95        render.renderTags(bar) catch |err| {
 96            log.err("renderTags failed for monitor {}: {s}", .{ tags.monitor.globalName, @errorName(err) });
 97            return;
 98        };
 99
100        bar.tags.surface.commit();
101        bar.background.surface.commit();
102    }
103}
104
105pub fn handleClick(self: *Tags, x: u32) !void {
106    const control = state.wayland.control.?;
107
108    if (self.monitor.bar) |bar| {
109        const index = x / bar.height;
110        const payload = try std.fmt.allocPrintZ(
111            state.gpa,
112            "{d}",
113            .{@as(u32, 1) << @as(u5, @intCast(index))},
114        );
115        defer state.gpa.free(payload);
116
117        control.addArgument("set-focused-tags");
118        control.addArgument(payload);
119        const callback = try control.runCommand(state.wayland.seat.?);
120        _ = callback;
121    }
122}