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;
  4const meta = std.meta;
  5const posix = std.posix;
  6
  7const wl = @import("wayland").client.wl;
  8const wp = @import("wayland").client.wp;
  9const zwlr = @import("wayland").client.zwlr;
 10const zriver = @import("wayland").client.zriver;
 11
 12const Bar = @import("Bar.zig");
 13const Input = @import("Input.zig");
 14const Monitor = @import("Monitor.zig");
 15const Seat = @import("Seat.zig");
 16const Wayland = @This();
 17
 18const state = &@import("root").state;
 19
 20display: *wl.Display,
 21registry: *wl.Registry,
 22fd: posix.fd_t,
 23
 24compositor: ?*wl.Compositor = null,
 25subcompositor: ?*wl.Subcompositor = null,
 26seat: ?*wl.Seat = null,
 27shm: ?*wl.Shm = null,
 28single_pixel_buffer_manager: ?*wp.SinglePixelBufferManagerV1 = null,
 29viewporter: ?*wp.Viewporter = null,
 30layer_shell: ?*zwlr.LayerShellV1 = null,
 31status_manager: ?*zriver.StatusManagerV1 = null,
 32control: ?*zriver.ControlV1 = null,
 33
 34river_seat: ?*Seat = null,
 35monitors: std.ArrayList(*Monitor),
 36inputs: std.ArrayList(*Input),
 37
 38pub fn init() !Wayland {
 39    const display = try wl.Display.connect(null);
 40    const wfd: posix.fd_t = @intCast(display.getFd());
 41    const registry = try display.getRegistry();
 42
 43    return Wayland{
 44        .display = display,
 45        .registry = registry,
 46        .fd = wfd,
 47        .monitors = std.ArrayList(*Monitor).init(state.gpa),
 48        .inputs = std.ArrayList(*Input).init(state.gpa),
 49    };
 50}
 51
 52pub fn deinit(self: *Wayland) void {
 53    for (self.monitors.items) |monitor| monitor.destroy();
 54    for (self.inputs.items) |input| input.destroy();
 55
 56    if (self.river_seat) |s| s.destroy();
 57    self.monitors.deinit();
 58    self.inputs.deinit();
 59
 60    if (self.compositor) |global| global.destroy();
 61    if (self.subcompositor) |global| global.destroy();
 62    if (self.shm) |global| global.destroy();
 63    if (self.viewporter) |global| global.destroy();
 64    if (self.single_pixel_buffer_manager) |global| global.destroy();
 65    if (self.layer_shell) |global| global.destroy();
 66    if (self.status_manager) |global| global.destroy();
 67    if (self.control) |global| global.destroy();
 68    // TODO: Do we need to .release() the seat?
 69    if (self.seat) |global| global.destroy();
 70
 71    self.registry.destroy();
 72    self.display.disconnect();
 73}
 74
 75pub fn registerGlobals(self: *Wayland) !void {
 76    self.registry.setListener(*Wayland, registryListener, self);
 77
 78    const errno = self.display.roundtrip();
 79    if (errno != .SUCCESS) {
 80        return error.RoundtripFailed;
 81    }
 82}
 83
 84pub fn findBar(self: *Wayland, wlSurface: ?*wl.Surface) ?*Bar {
 85    if (wlSurface == null) {
 86        return null;
 87    }
 88    for (self.monitors.items) |monitor| {
 89        if (monitor.bar) |bar| {
 90            if (bar.background.surface == wlSurface or
 91                bar.title.surface == wlSurface or
 92                bar.tags.surface == wlSurface or
 93                bar.text.surface == wlSurface)
 94            {
 95                return bar;
 96            }
 97        }
 98    }
 99    return null;
100}
101
102fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, self: *Wayland) void {
103    switch (event) {
104        .global => |g| {
105            self.bindGlobal(registry, g.name, g.interface) catch unreachable;
106        },
107        .global_remove => |g| {
108            for (self.monitors.items, 0..) |monitor, i| {
109                if (monitor.globalName == g.name) {
110                    self.monitors.swapRemove(i).destroy();
111                    break;
112                }
113            }
114            for (self.inputs.items, 0..) |input, i| {
115                if (input.globalName == g.name) {
116                    self.inputs.swapRemove(i).destroy();
117                    break;
118                }
119            }
120        },
121    }
122}
123
124fn bindGlobal(self: *Wayland, registry: *wl.Registry, name: u32, iface: [*:0]const u8) !void {
125    if (mem.orderZ(u8, iface, wl.Compositor.getInterface().name) == .eq) {
126        self.compositor = try registry.bind(name, wl.Compositor, 4);
127    } else if (mem.orderZ(u8, iface, wl.Subcompositor.getInterface().name) == .eq) {
128        self.subcompositor = try registry.bind(name, wl.Subcompositor, 1);
129    } else if (mem.orderZ(u8, iface, wl.Shm.getInterface().name) == .eq) {
130        self.shm = try registry.bind(name, wl.Shm, 1);
131    } else if (mem.orderZ(u8, iface, wp.Viewporter.getInterface().name) == .eq) {
132        self.viewporter = try registry.bind(name, wp.Viewporter, 1);
133    } else if (mem.orderZ(u8, iface, wp.SinglePixelBufferManagerV1.getInterface().name) == .eq) {
134        self.single_pixel_buffer_manager = try registry.bind(name, wp.SinglePixelBufferManagerV1, 1);
135    } else if (mem.orderZ(u8, iface, zwlr.LayerShellV1.getInterface().name) == .eq) {
136        self.layer_shell = try registry.bind(name, zwlr.LayerShellV1, 1);
137    } else if (mem.orderZ(u8, iface, zriver.StatusManagerV1.getInterface().name) == .eq) {
138        self.status_manager = try registry.bind(name, zriver.StatusManagerV1, 2);
139        self.river_seat = try Seat.create(); // TODO: find a better way to do this
140    } else if (mem.orderZ(u8, iface, zriver.ControlV1.getInterface().name) == .eq) {
141        self.control = try registry.bind(name, zriver.ControlV1, 1);
142    } else if (mem.orderZ(u8, iface, wl.Output.getInterface().name) == .eq) {
143        const monitor = try Monitor.create(registry, name);
144        try self.monitors.append(monitor);
145    } else if (mem.orderZ(u8, iface, wl.Seat.getInterface().name) == .eq) {
146        self.seat = try registry.bind(name, wl.Seat, 5);
147        try self.inputs.append(try Input.create(name));
148    }
149}