2zw

openbsd bat

d4e8e664cb4328d5cee693b0f0c9c2a7c483db48

SM <seb.michalk@gmail.com>

2026-01-09 20:38:38 +0000

 src/bar.zig | 74 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 59 insertions(+), 15 deletions(-)

diff --git a/src/bar.zig b/src/bar.zig
index f7dcde6..8f606b7 100644
--- a/src/bar.zig
+++ b/src/bar.zig
@@ -21,6 +21,7 @@
 // SOFTWARE.
 
 const std = @import("std");
+const builtin = @import("builtin");
 const ArrayList = std.array_list.Managed;
 const C = @import("c.zig").C;
 const drawlib = @import("draw.zig");
@@ -451,32 +452,75 @@ fn clkUpd(mod: *Mod, alloc: std.mem.Allocator, now_ms: i64) !void {
 }
 
 fn batUpd(mod: *Mod, alloc: std.mem.Allocator, _: i64) !void {
+    const capacity = readbat(alloc) catch {
+        try mod.set("N/A");
+        return;
+    };
+
+    var buffer: [16]u8 = undefined;
+    const text = try std.fmt.bufPrint(&buffer, "BAT {d}%", .{capacity});
+    const owned = try alloc.dupe(u8, text);
+    defer alloc.free(owned);
+    try mod.set(owned);
+}
+
+fn readbat(alloc: std.mem.Allocator) !u8 {
+    return switch (builtin.target.os.tag) {
+        .openbsd => readbat_openbsd(alloc),
+        else => readbat_linux(),
+    };
+}
+
+fn readbat_linux() !u8 {
     const capacity_path = "/sys/class/power_supply/BAT0/capacity";
     const file = std.fs.openFileAbsolute(capacity_path, .{}) catch |err| {
-        if (err == error.FileNotFound) {
-            try mod.set("N/A");
-            return;
-        }
+        if (err == error.FileNotFound) return error.FileNotFound;
         return err;
     };
     defer file.close();
 
     var buf: [8]u8 = undefined;
     const bytes_read = try file.readAll(&buf);
-    const content = std.mem.trimRight(
-        u8,
-        buf[0..bytes_read],
-        &std.ascii.whitespace,
-    );
-    const capacity = try std.fmt.parseInt(u8, content, 10);
+    if (bytes_read == 0) return error.EndOfStream;
 
-    var buffer: [16]u8 = undefined;
-    const text = try std.fmt.bufPrint(&buffer, "BAT {d}%", .{capacity});
-    const owned = try alloc.dupe(u8, text);
-    defer alloc.free(owned);
-    try mod.set(owned);
+    const content = std.mem.trim(u8, buf[0..bytes_read], &std.ascii.whitespace);
+    return std.fmt.parseInt(u8, content, 10);
 }
 
+fn readbat_openbsd(alloc: std.mem.Allocator) !u8 {
+    var child = std.process.Child.init(&.{ "sysctl", "-n", "hw.sensors.acpibat0.raw0" }, alloc);
+    defer child.deinit();
+    child.stdout_behavior = .Pipe;
+    child.stderr_behavior = .Ignore;
+    try child.spawn();
+
+    const stdout_file = child.stdout orelse return error.MissingStdout;
+    defer stdout_file.close();
+
+    var buf: [64]u8 = undefined;
+    const read_bytes = try stdout_file.readAll(&buf);
+    _ = try child.wait();
+    if (read_bytes == 0) return error.EndOfStream;
+
+    const trimmed = std.mem.trim(u8, buf[0..read_bytes], &std.ascii.whitespace);
+
+    // Happy path: sysctl prints "XX.XX%" or "XX%".
+    if (std.mem.indexOfScalar(u8, trimmed, '%')) |percent_idx| {
+        const number_slice = trimmed[0..percent_idx];
+        return parsePercent(number_slice);
+    }
+
+    return error.Invalid;
+}
+
+fn parsePercent(text: []const u8) !u8 {
+    const number_text = std.mem.trim(u8, text, &std.ascii.whitespace);
+    if (number_text.len == 0) return error.Invalid;
+    const value = try std.fmt.parseFloat(f32, number_text);
+    return std.math.clamp(@as(i32, @intFromFloat(value + 0.5)), 0, 100);
+}
+
+
 var g_bar: ?*Bar = null;
 
 pub fn setGlobalBar(bar_ptr: ?*Bar) void {