From cf731dbce13bd5c07c58086b9cbcec14acf4af48 Mon Sep 17 00:00:00 2001 From: Lars Engels Date: Sun, 5 Jul 2026 21:19:30 +0200 Subject: [PATCH] Add battery capacity support on FreeBSD (#988) ## What are the changes about? So far ly only supported showing battery capacity on Linux. This adds support for FreeBSD as well using `sysctlbyname()` instead of reading from `/sys/...` ## What existing issue does this resolve? No battery shown on FreeBSD. _Replace this with a reference to an existing issue, or N/A if there is none_ ## Pre-requisites - [x] I have tested & confirmed the changes work locally - [x] I have run `zig fmt` throughout my changes Co-authored-by: AnErrupTion Co-authored-by: AnErrupTion Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/988 Reviewed-by: AnErrupTion --- ly-core/build.zig | 1 + ly-core/src/interop.zig | 3 +++ res/config.ini | 1 + src/main.zig | 33 ++++++++++++++++++++++++--------- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ly-core/build.zig b/ly-core/build.zig index cf0dbf8..75ecb3f 100644 --- a/ly-core/build.zig +++ b/ly-core/build.zig @@ -51,6 +51,7 @@ pub fn build(b: *std.Build) void { } else if (target.result.os.tag == .freebsd) { addCImport(b, mod, translate_c, target, optimize, "kbio", "#include "); addCImport(b, mod, translate_c, target, optimize, "consio", "#include "); + addCImport(b, mod, translate_c, target, optimize, "sysctl", "#include "); } const mod_tests = b.addTest(.{ diff --git a/ly-core/src/interop.zig b/ly-core/src/interop.zig index 7d09381..736d8ca 100644 --- a/ly-core/src/interop.zig +++ b/ly-core/src/interop.zig @@ -14,6 +14,9 @@ pub const utmp = @import("utmp"); // Exists for X11 support only pub const xcb = @import("xcb"); +// FreeBSD only +pub const sysctl = @import("sysctl"); + pub const TimeOfDay = struct { seconds: i64, microseconds: i64, diff --git a/res/config.ini b/res/config.ini index 169f5ea..4269e7f 100644 --- a/res/config.ini +++ b/res/config.ini @@ -50,6 +50,7 @@ auth_fails = 10 # Identifier for battery whose charge to display at top left # Primary battery is usually BAT0 or BAT1 # If set to null, battery status won't be shown +# Unused on FreeBSD (a sysctl is used there) battery_id = null # Automatic login configuration diff --git a/src/main.zig b/src/main.zig index 0e57d41..91f3af8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2340,19 +2340,34 @@ fn adjustBrightness(io: std.Io, cmd: []const u8) !void { } fn getBatteryPercentage(io: std.Io, battery_id: []const u8) !u8 { - const path = try std.fmt.allocPrint(temporary_allocator, "/sys/class/power_supply/{s}/capacity", .{battery_id}); - defer temporary_allocator.free(path); + if (builtin.os.tag == .freebsd) { + // battery_id is unused on FreeBSD; sysctl exposes a single aggregate value. + // For multi-battery systems the MIB would be hw.acpi.battery.N.life, + // but hw.acpi.battery.life is the standard single-battery interface. + var capacity: c_int = -1; + var size: usize = @sizeOf(c_int); - const battery_file = try std.Io.Dir.cwd().openFile(io, path, .{}); - defer battery_file.close(io); + const ret = interop.sysctl.sysctlbyname("hw.acpi.battery.life", &capacity, &size, null, 0); - var buffer: [8]u8 = undefined; - const bytes_read = try battery_file.readStreaming(io, &.{&buffer}); - const capacity_str = buffer[0..bytes_read]; + if (ret != 0) return error.SysctlFailed; + if (capacity < 0 or capacity > 100) return error.InvalidBatteryCapacity; - const trimmed = std.mem.trimEnd(u8, capacity_str, "\n\r"); + return @intCast(capacity); + } else { + const path = try std.fmt.allocPrint(temporary_allocator, "/sys/class/power_supply/{s}/capacity", .{battery_id}); + defer temporary_allocator.free(path); - return try std.fmt.parseInt(u8, trimmed, 10); + const battery_file = try std.Io.Dir.cwd().openFile(io, path, .{}); + defer battery_file.close(io); + + var buffer: [8]u8 = undefined; + const bytes_read = try battery_file.readStreaming(io, &.{&buffer}); + const capacity_str = buffer[0..bytes_read]; + + const trimmed = std.mem.trimEnd(u8, capacity_str, "\n\r"); + + return try std.fmt.parseInt(u8, trimmed, 10); + } } fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {