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