commit 8bdf5f7d0900c6a078ba5ab24ea58c9a68f7815f
Author: Jeeves <guydoodlesdev@gmail.com>
Date:   Fri Mar 15 18:30:35 2024 -0600

    initial commit

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6313b56
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..feda423
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# This file is for zig-specific build artifacts.
+# If you have OS-specific or editor-specific files to ignore,
+# such as *.swp or .DS_Store, put those in your global
+# ~/.gitignore and put this in your ~/.gitconfig:
+#
+# [core]
+#     excludesfile = ~/.gitignore
+#
+# Cheers!
+# -andrewrk
+
+zig-cache/
+zig-out/
+/release/
+/debug/
+/build/
+/build-*/
+/docgen_tmp/
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..1e7dade
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,70 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{});
+
+    // Standard optimization options allow the person running `zig build` to select
+    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+    // set a preferred release mode, allowing the user to decide how to optimize.
+    const optimize = b.standardOptimizeOption(.{});
+
+    const exe = b.addExecutable(.{
+        .name = "default",
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // This declares intent for the executable to be installed into the
+    // standard location when the user invokes the "install" step (the default
+    // step when running `zig build`).
+    b.installArtifact(exe);
+
+    // This *creates* a Run step in the build graph, to be executed when another
+    // step is evaluated that depends on it. The next line below will establish
+    // such a dependency.
+    const run_cmd = b.addRunArtifact(exe);
+
+    // By making the run step depend on the install step, it will be run from the
+    // installation directory rather than directly from within the cache directory.
+    // This is not necessary, however, if the application depends on other installed
+    // files, this ensures they will be present and in the expected location.
+    run_cmd.step.dependOn(b.getInstallStep());
+
+    // This allows the user to pass arguments to the application in the build
+    // command itself, like this: `zig build run -- arg1 arg2 etc`
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build run`
+    // This will evaluate the `run` step rather than the default, which is "install".
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+
+    // Creates a step for unit testing. This only builds the test executable
+    // but does not run it.
+    const unit_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const run_unit_tests = b.addRunArtifact(unit_tests);
+
+    // Similar to creating the run step earlier, this exposes a `test` step to
+    // the `zig build --help` menu, providing a way for the user to request
+    // running the unit tests.
+    const test_step = b.step("test", "Run unit tests");
+    test_step.dependOn(&run_unit_tests.step);
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..c5e0267
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,78 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1706063522,
+        "narHash": "sha256-o1m9en7ovSjyktXgX3n/6GJEwG06WYa/9Mfx5hTTf5g=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "95c1439b205d507f3cb88aae76e02cd6a01ac504",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "zig2nix": "zig2nix"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "zig2nix": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      },
+      "locked": {
+        "lastModified": 1710206277,
+        "narHash": "sha256-LHQaQsSaH6IZ1YPF25gMWxFK7DWOn1wMkdH5WQNvEvU=",
+        "owner": "Cloudef",
+        "repo": "zig2nix",
+        "rev": "a9b2a0546e6dc330042aa5d19269ab9c1cb6fb5f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "Cloudef",
+        "repo": "zig2nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..e35bb56
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,82 @@
+{
+  description = "Zig project flake";
+
+  inputs = {
+    zig2nix.url = "github:Cloudef/zig2nix";
+  };
+
+  outputs = { zig2nix, ... }: let
+    flake-utils = zig2nix.inputs.flake-utils;
+  in (flake-utils.lib.eachDefaultSystem (system: let
+      # Zig flake helper
+      # Check the flake.nix in zig2nix project for more options:
+      # <https://github.com/Cloudef/zig2nix/blob/master/flake.nix>
+      env = zig2nix.outputs.zig-env.${system} {};
+      system-triple = env.lib.zigTripleFromString system;
+    in with builtins; with env.lib; with env.pkgs.lib; rec {
+      # nix build .#target.{zig-target}
+      # e.g. nix build .#target.x86_64-linux-gnu
+      packages.target = genAttrs allTargetTriples (target: env.packageForTarget target ({
+        src = cleanSource ./.;
+
+        nativeBuildInputs = with env.pkgs; [];
+        buildInputs = with env.pkgsForTarget target; [];
+
+        # Smaller binaries and avoids shipping glibc.
+        zigPreferMusl = true;
+
+        # This disables LD_LIBRARY_PATH mangling, binary patching etc...
+        # The package won't be usable inside nix.
+        zigDisableWrap = true;
+      } // optionalAttrs (!pathExists ./build.zig.zon) {
+        pname = "my-zig-project";
+        version = "0.0.0";
+      }));
+
+      # nix build .
+      packages.default = packages.target.${system-triple}.override {
+        # Prefer nix friendly settings.
+        zigPreferMusl = false;
+        zigDisableWrap = false;
+      };
+
+      # For bundling with nix bundle for running outside of nix
+      # example: https://github.com/ralismark/nix-appimage
+      apps.bundle.target = genAttrs allTargetTriples (target: let
+        pkg = packages.target.${target};
+      in {
+        type = "app";
+        program = "${pkg}/bin/default";
+      });
+
+      # default bundle
+      apps.bundle.default = apps.bundle.target.${system-triple};
+
+      # nix run .
+      apps.default = env.app [] "zig build run -- \"$@\"";
+
+      # nix run .#build
+      apps.build = env.app [] "zig build \"$@\"";
+
+      # nix run .#test
+      apps.test = env.app [] "zig build test -- \"$@\"";
+
+      # nix run .#docs
+      apps.docs = env.app [] "zig build docs -- \"$@\"";
+
+      # nix run .#deps
+      apps.deps = env.showExternalDeps;
+
+      # nix run .#zon2json
+      apps.zon2json = env.app [env.zon2json] "zon2json \"$@\"";
+
+      # nix run .#zon2json-lock
+      apps.zon2json-lock = env.app [env.zon2json-lock] "zon2json-lock \"$@\"";
+
+      # nix run .#zon2nix
+      apps.zon2nix = env.app [env.zon2nix] "zon2nix \"$@\"";
+
+      # nix develop
+      devShells.default = env.mkShell {};
+    }));
+}
diff --git a/result b/result
new file mode 120000
index 0000000..ab3638b
--- /dev/null
+++ b/result
@@ -0,0 +1 @@
+/nix/store/lj5v0m03905nm4v3n1ln8q8hj50hlm69-my-zig-project-0.0.0
\ No newline at end of file
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..3e5e8e9
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+const io = std.io;
+const heap = std.heap;
+const json = std.json;
+
+const battery = @import("modules/battery.zig");
+const calendar = @import("modules/calendar.zig");
+const display = @import("modules/display.zig");
+const uptime = @import("modules/uptime.zig");
+const Module = @import("module.zig");
+
+pub fn main() !void {
+    var arena = heap.ArenaAllocator.init(heap.page_allocator);
+    defer arena.deinit();
+
+    const stdout_file = std.io.getStdOut().writer();
+    var bw = std.io.bufferedWriter(stdout_file);
+    const stdout = bw.writer();
+
+    try stdout.print("{{\"version\":1}}\n[\n", .{});
+    try bw.flush();
+
+    var _display = display.init(arena.allocator());
+    var _battery = battery.init(arena.allocator());
+    var _calendar = calendar.init(arena.allocator());
+    var modules = [_]Module{
+        _display.module,
+        _battery.module,
+        _calendar.module,
+    };
+
+    while (true) {
+        try stdout.print("[", .{});
+        for (modules) |module| {
+            try json.stringify(try module.getJson(), .{ .emit_null_optional_fields = false }, stdout);
+            try stdout.print(",", .{});
+        }
+        // try stdout.print("[", .{});
+        // try json.stringify(battery_json, .{ .emit_null_optional_fields = false }, stdout);
+        // try stdout.print(",", .{});
+        // try json.stringify(calendar_json, .{ .emit_null_optional_fields = false }, stdout);
+        // try stdout.print(",", .{});
+        // try stdout.print("{{\"full_text\":\"test\"}}", .{});
+        try stdout.print("],\n", .{});
+
+        try bw.flush();
+        _ = arena.reset(.retain_capacity);
+        std.time.sleep(1000_000_000);
+    }
+}
diff --git a/src/module.zig b/src/module.zig
new file mode 100644
index 0000000..46ad834
--- /dev/null
+++ b/src/module.zig
@@ -0,0 +1,30 @@
+const std = @import("std");
+
+pub const JSON = struct {
+    full_text: []const u8,
+    short_text: ?[]const u8 = null,
+    color: ?[]const u8 = null,
+    background: ?[]const u8 = null,
+    border: ?[]const u8 = null,
+    border_top: ?u16 = null,
+    border_right: ?u16 = null,
+    border_bottom: ?u16 = null,
+    border_left: ?u16 = null,
+    min_width: ?u16 = null,
+    @"align": ?[]const u8 = null,
+    name: ?[]const u8 = null,
+    instance: ?[]const u8 = null,
+    urgent: ?bool = null,
+    separator: ?bool = null,
+    separator_block_width: ?u16 = null,
+    markup: ?[]const u8 = null,
+};
+
+const Self = @This();
+
+allocator: std.mem.Allocator,
+getJsonFn: *const fn (*const Self) anyerror!JSON,
+
+pub fn getJson(self: *const Self) anyerror!JSON {
+    return self.getJsonFn(self);
+}
diff --git a/src/modules/battery.zig b/src/modules/battery.zig
new file mode 100644
index 0000000..76b0c1b
--- /dev/null
+++ b/src/modules/battery.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+const Module = @import("../module.zig");
+const Self = @This();
+
+module: Module,
+
+pub fn init(allocator: std.mem.Allocator) Self {
+    return .{
+        .module = .{
+            .allocator = allocator,
+            .getJsonFn = getJson,
+        },
+    };
+}
+
+pub fn getJson(module: *const Module) !Module.JSON {
+    const self = @fieldParentPtr(Self, "module", module);
+
+    var energy_full_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/energy_full", .{});
+    defer energy_full_file.close();
+    var energy_now_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/energy_now", .{});
+    defer energy_now_file.close();
+    var status_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/status", .{});
+    defer status_file.close();
+
+    const energy_full_string = try energy_full_file.reader().readAllAlloc(self.module.allocator, 32);
+    const energy_now_string = try energy_now_file.reader().readAllAlloc(self.module.allocator, 32);
+    const status_string = try status_file.reader().readAllAlloc(self.module.allocator, 32);
+    const energy_full = try std.fmt.parseInt(u32, energy_full_string[0 .. energy_full_string.len - 1], 10);
+    const energy_now = try std.fmt.parseInt(u32, energy_now_string[0 .. energy_now_string.len - 1], 10);
+
+    const status = if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Full"))
+        "="
+    else if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Discharging"))
+        "v"
+    else if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Charging"))
+        "^"
+    else
+        "?";
+    const percent_left = @as(f32, @floatFromInt(energy_now)) / @as(f32, @floatFromInt(energy_full)) * 100;
+
+    return .{
+        .full_text = try std.fmt.allocPrint(self.module.allocator, "{s} {d:.2}%", .{ status, percent_left }),
+    };
+}
diff --git a/src/modules/calendar.zig b/src/modules/calendar.zig
new file mode 100644
index 0000000..389741a
--- /dev/null
+++ b/src/modules/calendar.zig
@@ -0,0 +1,42 @@
+const std = @import("std");
+const Module = @import("../module.zig");
+const Self = @This();
+
+module: Module,
+
+pub fn init(allocator: std.mem.Allocator) Self {
+    return .{
+        .module = .{
+            .allocator = allocator,
+            .getJsonFn = getJson,
+        },
+    };
+}
+
+pub fn getJson(module: *const Module) !Module.JSON {
+    const self = @fieldParentPtr(Self, "module", module);
+    var tz_file = try std.fs.openFileAbsolute("/etc/localtime", .{});
+    defer tz_file.close();
+    var tz = try std.tz.Tz.parse(self.module.allocator, tz_file.reader());
+    defer tz.deinit();
+    // std.debug.print("{any}\n", .{tz.timetypes});
+
+    const timestamp = std.time.timestamp() + tz.timetypes[tz.timetypes.len - 1].offset + std.time.s_per_day;
+
+    const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = @intCast(timestamp) };
+    const day_seconds = epoch_seconds.getDaySeconds();
+    const epoch_day = epoch_seconds.getEpochDay();
+    const year_day = epoch_day.calculateYearDay();
+    const month_day = year_day.calculateMonthDay();
+
+    return .{
+        .full_text = try std.fmt.allocPrint(self.module.allocator, "{d}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}", .{
+            year_day.year,
+            @intFromEnum(month_day.month),
+            month_day.day_index,
+            day_seconds.getHoursIntoDay(),
+            day_seconds.getMinutesIntoHour(),
+            day_seconds.getSecondsIntoMinute(),
+        }),
+    };
+}
diff --git a/src/modules/display.zig b/src/modules/display.zig
new file mode 100644
index 0000000..34fba25
--- /dev/null
+++ b/src/modules/display.zig
@@ -0,0 +1,34 @@
+const std = @import("std");
+const Module = @import("../module.zig");
+const Self = @This();
+
+module: Module,
+
+pub fn init(allocator: std.mem.Allocator) Self {
+    return .{
+        .module = .{
+            .allocator = allocator,
+            .getJsonFn = getJson,
+        },
+    };
+}
+
+pub fn getJson(module: *const Module) !Module.JSON {
+    const self = @fieldParentPtr(Self, "module", module);
+
+    var brightness_full_file = try std.fs.openFileAbsolute("/sys/class/backlight/acpi_video0/max_brightness", .{});
+    defer brightness_full_file.close();
+    var brightness_now_file = try std.fs.openFileAbsolute("/sys/class/backlight/acpi_video0/actual_brightness", .{});
+    defer brightness_now_file.close();
+
+    const brightness_full_string = try brightness_full_file.reader().readAllAlloc(self.module.allocator, 32);
+    const brightness_now_string = try brightness_now_file.reader().readAllAlloc(self.module.allocator, 32);
+    const brightness_full = try std.fmt.parseInt(u32, brightness_full_string[0 .. brightness_full_string.len - 1], 10);
+    const brightness_now = try std.fmt.parseInt(u32, brightness_now_string[0 .. brightness_now_string.len - 1], 10);
+
+    const percent = @as(f32, @floatFromInt(brightness_now)) / @as(f32, @floatFromInt(brightness_full)) * 100;
+
+    return .{
+        .full_text = try std.fmt.allocPrint(self.module.allocator, "{d:.2}%", .{percent}),
+    };
+}
diff --git a/src/modules/uptime.zig b/src/modules/uptime.zig
new file mode 100644
index 0000000..c74e3f9
--- /dev/null
+++ b/src/modules/uptime.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+const Module = @import("../module.zig");
+const Self = @This();
+
+module: Module,
+
+pub fn init(allocator: std.mem.Allocator) Module {
+    return .{
+        .module = .{
+            .allocator = allocator,
+            .getJsonFn = getJson,
+        },
+    };
+}
+
+pub fn getJson(module: *const Module) !Module.JSON {
+    const self = @fieldParentPtr(Self, "module", module);
+
+    var energy_full_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/energy_full", .{});
+    defer energy_full_file.close();
+    var energy_now_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/energy_now", .{});
+    defer energy_now_file.close();
+    var status_file = try std.fs.openFileAbsolute("/sys/class/power_supply/BAT0/status", .{});
+    defer status_file.close();
+
+    const energy_full_string = try energy_full_file.reader().readAllAlloc(self.module.allocator, 32);
+    const energy_now_string = try energy_now_file.reader().readAllAlloc(self.module.allocator, 32);
+    const status_string = try status_file.reader().readAllAlloc(self.module.allocator, 32);
+    const energy_full = try std.fmt.parseInt(u32, energy_full_string[0 .. energy_full_string.len - 1], 10);
+    const energy_now = try std.fmt.parseInt(u32, energy_now_string[0 .. energy_now_string.len - 1], 10);
+
+    const status = if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Full"))
+        "="
+    else if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Discharging"))
+        "v"
+    else if (std.mem.eql(u8, status_string[0 .. status_string.len - 1], "Charging"))
+        "^"
+    else
+        "?";
+    const percent_left = @as(f32, @floatFromInt(energy_now)) / @as(f32, @floatFromInt(energy_full)) * 100;
+
+    return .{
+        .full_text = try std.fmt.allocPrint(self.module.allocator, "{s} {d:.2}%", .{ status, percent_left }),
+    };
+}