creek

A malleable and minimalist status bar for the River compositor

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

  1const std = @import("std");
  2const heap = std.heap;
  3const io = std.io;
  4const log = std.log;
  5const mem = std.mem;
  6const posix = std.posix;
  7const os = std.os;
  8const fmt = std.fmt;
  9const process = std.process;
 10
 11const fcft = @import("fcft");
 12const pixman = @import("pixman");
 13
 14const flags = @import("flags.zig");
 15const Loop = @import("Loop.zig");
 16const Wayland = @import("Wayland.zig");
 17
 18pub const Config = struct {
 19    height: u16,
 20    normalFgColor: pixman.Color,
 21    normalBgColor: pixman.Color,
 22    focusFgColor: pixman.Color,
 23    focusBgColor: pixman.Color,
 24    font: *fcft.Font,
 25};
 26
 27pub const State = struct {
 28    gpa: mem.Allocator,
 29    config: Config,
 30    wayland: Wayland,
 31    loop: Loop,
 32};
 33
 34pub var state: State = undefined;
 35
 36fn parseColor(str: []const u8) !pixman.Color {
 37    // Color string needs to contain a base prefix.
 38    // For example: 0xRRGGBB.
 39    const val = try fmt.parseInt(u24, str, 0);
 40
 41    const r: u8 = @truncate(val >> 16);
 42    const g: u8 = @truncate(val >> 8);
 43    const b: u8 = @truncate(val);
 44
 45    return pixman.Color{
 46        .red = @as(u16, r) << 8 | 0xff,
 47        .green = @as(u16, g) << 8 | 0xff,
 48        .blue = @as(u16, b) << 8 | 0xff,
 49        .alpha = 0xffff,
 50    };
 51}
 52
 53fn parseColorFlag(flg: ?[]const u8, def: []const u8) !pixman.Color {
 54    if (flg) |raw| {
 55        return parseColor(raw);
 56    } else {
 57        return parseColor(def);
 58    }
 59}
 60
 61fn parseFlags(args: [][*:0]u8) !Config {
 62    const result = flags.parser([*:0]const u8, &.{
 63        .{ .name = "hg", .kind = .arg }, // height
 64        .{ .name = "fn", .kind = .arg }, // font name
 65        .{ .name = "nf", .kind = .arg }, // normal foreground
 66        .{ .name = "nb", .kind = .arg }, // normal background
 67        .{ .name = "ff", .kind = .arg }, // focused foreground
 68        .{ .name = "fb", .kind = .arg }, // focused background
 69    }).parse(args) catch {
 70        usage();
 71    };
 72
 73    var font_names = if (result.flags.@"fn") |raw| blk: {
 74        break :blk [_][*:0]const u8{raw};
 75    } else blk: {
 76        break :blk [_][*:0]const u8{"monospace:size=10"};
 77    };
 78
 79    const font = try fcft.Font.fromName(&font_names, null);
 80    const height: u16 = if (result.flags.hg) |raw| blk: {
 81        break :blk try fmt.parseUnsigned(u16, raw, 10);
 82    } else blk: {
 83        break :blk @intFromFloat(@as(f32, @floatFromInt(font.height)) * 1.5);
 84    };
 85
 86    return Config{
 87        .font = font,
 88        .height = @intCast(height),
 89        .normalFgColor = try parseColorFlag(result.flags.nf, "0xb8b8b8"),
 90        .normalBgColor = try parseColorFlag(result.flags.nb, "0x282828"),
 91        .focusFgColor = try parseColorFlag(result.flags.ff, "0x181818"),
 92        .focusBgColor = try parseColorFlag(result.flags.fb, "0x7cafc2"),
 93    };
 94}
 95
 96pub fn usage() noreturn {
 97    const desc =
 98        \\usage: creek [-hg HEIGHT] [-fn FONT] [-nf COLOR] [-nb COLOR]
 99        \\             [-ff COLOR] [-fb COLOR]
100        \\
101    ;
102
103    io.getStdErr().writeAll(desc) catch |err| {
104        std.debug.panic("{s}", .{@errorName(err)});
105    };
106
107    process.exit(1);
108}
109
110pub fn main() anyerror!void {
111    var gpa: heap.GeneralPurposeAllocator(.{}) = .{};
112    defer _ = gpa.deinit();
113
114    _ = fcft.init(.auto, false, .warning);
115    if (fcft.capabilities() & fcft.Capabilities.text_run_shaping == 0) {
116        @panic("Support for text run shaping required in fcft and not present");
117    }
118
119    state.gpa = gpa.allocator();
120    state.wayland = try Wayland.init();
121    state.loop = try Loop.init();
122    state.config = parseFlags(os.argv[1..]) catch |err| {
123        log.err("Option parsing failed with: {s}", .{@errorName(err)});
124        usage();
125    };
126
127    defer {
128        state.wayland.deinit();
129    }
130
131    try state.wayland.registerGlobals();
132    try state.loop.run();
133}