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 <anerruption+codeberg@disroot.org>
Co-authored-by: AnErrupTion <anerruption@disroot.org>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/988
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
This commit is contained in:
Lars Engels 2026-07-05 21:19:30 +02:00 committed by AnErrupTion
parent 1d272ff74f
commit cf731dbce1
4 changed files with 29 additions and 9 deletions

View File

@ -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 <sys/kbio.h>");
addCImport(b, mod, translate_c, target, optimize, "consio", "#include <sys/consio.h>");
addCImport(b, mod, translate_c, target, optimize, "sysctl", "#include <sys/sysctl.h>");
}
const mod_tests = b.addTest(.{

View File

@ -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,

View File

@ -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

View File

@ -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 {