commit ba1a6566f871342e3f1f43076b0763bbe6643e1e
parent f41ccd41067e344da56cda541608f59fe753c30e
Author: brookjeynes <me@brookjeynes.dev>
Date: Fri, 15 May 2026 08:13:06 +1000
feat: upgrade to zig 0.16.0
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
11 files changed, 372 insertions(+), 455 deletions(-)
diff --git a/.github/workflows/create-draft-release.yml b/.github/workflows/create-draft-release.yml
@@ -1,67 +0,0 @@
-name: Create draft release
-
-on:
- workflow_dispatch:
-
-env:
- # https://github.com/cli/cli/issues/9514#issuecomment-2311517523
- GH_TOKEN: ${{ secrets.TOKEN }}
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Set up Zig
- uses: korandoru/setup-zig@v1
- with:
- zig-version: "master"
-
- - name: Build application
- run: |
- zig build -Dall-targets
-
- - name: Upload artifact
- uses: actions/upload-artifact@v4
- with:
- name: builds
- path: zig-out/
-
- release:
- needs: build
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Download artifacts
- uses: actions/download-artifact@v4
- with:
- path: artifacts
-
- - name: Create tarballs
- run: |
- mkdir -p tarballs
- for dir in artifacts/builds/*; do
- if [ -d "$dir" ] && [ $(basename "$dir") != "bin" ]; then
- tar -czf "tarballs/$(basename "$dir").tar.gz" -C "$dir" .
- fi
- done
-
- - name: gh log
- run: |
- gh --version
- gh auth status
-
- - name: Create release
- run: |
- gh release create ${{ github.ref_name }} tarballs/* \
- --title "Release ${{ github.ref_name }}" \
- --notes "Automated release with build artifacts." \
- --draft
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,3 @@
zig-out/
.zig-cache/
+zig-pkg/
diff --git a/README b/README
@@ -0,0 +1,20 @@
+lens
+====
+
+a minimal posix system fetch tool
+
+user@fedora
+--------------
+distro Fedora Linux 40 (Workstation Edition) x86_64
+uptime 6d 13h 57m
+kernel 6.10.12-200.fc40.x86_64
+desktop river
+shell zsh
+memory 17170 / 63572 MB (27%)
+battery 96% (Charging)
+cpu AMD Ryzen 7 7840U w/ Radeon 780M Graphics
+disk 30G / 1.9T (2%)
+
+lens will look for a configuration file at either:
+- $HOME/.lens/config.ziggy
+- $XDG_CONFIG_HOME/lens/config.ziggy
diff --git a/README.md b/README.md
@@ -1,31 +0,0 @@
-lens
------
-
-A simple unix system fetch tool.
-
-```
-user@fedora
---------------
-distro Fedora Linux 40 (Workstation Edition) x86_64
-uptime 6d 13h 57m
-kernel 6.10.12-200.fc40.x86_64
-desktop river
-shell zsh
-memory 17170 / 63572 MB (27%)
-battery 96% (Charging)
-cpu AMD Ryzen 7 7840U w/ Radeon 780M Graphics
-disk 30G / 1.9T (2%)
-```
-
-Configure `lens` by editing the external configuration file located at either:
-- `$HOME/.lens/config.ziggy`
-- `$XDG_CONFIG_HOME/lens/config.ziggy`.
-
-lens will look for these env variables specifically. If they are not set, lens
-will not be able to find the config file.
-
-An example config file can be found [here](./example-config.ziggy).
-The config schema can be found [here](./config.ziggy-schema).
-
-Contributions, issues, and feature requests are always welcome! This project
-is built using Zig `v0.14.0-dev.2628+5b5c60f43`.
diff --git a/build.zig b/build.zig
@@ -28,7 +28,6 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
- // Build targets for release.
const build_all = b.option(bool, "all-targets", "Build all targets in ReleaseSafe mode.") orelse false;
if (build_all) {
try build_targets(b);
diff --git a/build.zig.zon b/build.zig.zon
@@ -2,12 +2,12 @@
.fingerprint = 0x2cdaf8c397088e72,
.name = .lens,
.version = "0.2.0",
- .minimum_zig_version = "0.15.2",
+ .minimum_zig_version = "0.16.0",
.dependencies = .{
.ziggy = .{
- .url = "https://github.com/kristoff-it/ziggy/archive/4353b20ef2ac750e35c6d68e4eb2a07c2d7cf901.tar.gz",
- .hash = "ziggy-0.1.0-kTg8v5pABgDztlefWHceH-Sh8tVveguFC61QkmLkIRaA",
+ .url = "git+https://github.com/kristoff-it/ziggy#0b61581980d6905dd4108a618d602f5778593122",
+ .hash = "ziggy-0.1.0-kTg8v0NWBgAyFtS5awtOlKnC7ddvefR_I0Xh3XQyi7p1",
},
},
diff --git a/src/environment.zig b/src/environment.zig
@@ -1,23 +1,24 @@
const std = @import("std");
-pub fn getHomeDir() !?std.fs.Dir {
- return try std.fs.openDirAbsolute(std.posix.getenv("HOME") orelse {
+pub fn getHomeDir(io: std.Io, env_map: *std.process.Environ.Map) !?std.Io.Dir {
+ return try std.Io.Dir.openDirAbsolute(io, env_map.get("HOME") orelse {
return null;
}, .{ .iterate = true });
}
-pub fn getXdgConfigHomeDir() !?std.fs.Dir {
- return try std.fs.openDirAbsolute(std.posix.getenv("XDG_CONFIG_HOME") orelse {
+pub fn getXdgConfigHomeDir(io: std.Io, env_map: *std.process.Environ.Map) !?std.Io.Dir {
+ return try std.Io.Dir.openDirAbsolute(io, env_map.get("XDG_CONFIG_HOME") orelse {
return null;
}, .{ .iterate = true });
}
-pub fn fileExists(dir: std.fs.Dir, path: []const u8) bool {
+pub fn fileExists(dir: std.Io.Dir, io: std.Io, path: []const u8) bool {
const result = blk: {
- _ = dir.openFile(path, .{}) catch |err| {
+ _ = dir.openFile(io, path, .{}) catch |err| {
switch (err) {
error.FileNotFound => break :blk false,
else => {
+ std.log.info("{}", .{err});
break :blk true;
},
}
@@ -27,9 +28,9 @@ pub fn fileExists(dir: std.fs.Dir, path: []const u8) bool {
return result;
}
-pub fn dirExists(dir: std.fs.Dir, path: []const u8) bool {
+pub fn dirExists(dir: std.Io.Dir, io: std.Io, path: []const u8) bool {
const result = blk: {
- _ = dir.openDir(path, .{}) catch |err| {
+ _ = dir.openDir(io, path, .{}) catch |err| {
switch (err) {
error.FileNotFound => break :blk false,
else => {
diff --git a/src/main.zig b/src/main.zig
@@ -43,50 +43,51 @@ const CONFIG_NAME = "config.ziggy";
const HOME_DIR_NAME = ".lens";
const XDG_CONFIG_HOME_DIR_NAME = "lens";
-pub fn main() !void {
+pub fn main(init: std.process.Init) !void {
+ const gpa = init.gpa;
+ const io = init.io;
+
var stdout_buffer: [1024]u8 = undefined;
- var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
- const stdout = &stdout_writer.interface;
+ var stdout_file_writer: std.Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
+ const stdout_writer = &stdout_file_writer.interface;
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- defer _ = gpa.deinit();
- var arena = std.heap.ArenaAllocator.init(gpa.allocator());
+ var arena = std.heap.ArenaAllocator.init(gpa);
defer _ = arena.deinit();
- var alloc = arena.allocator();
+ var allocator = arena.allocator();
// Read config
const config = lbl: {
var config_dir = dir: {
- if (try environment.getXdgConfigHomeDir()) |home_dir| {
+ if (try environment.getXdgConfigHomeDir(io, init.environ_map)) |home_dir| {
defer {
var dir = home_dir;
- dir.close();
+ dir.close(io);
}
- if (!environment.dirExists(home_dir, XDG_CONFIG_HOME_DIR_NAME)) {
+ if (!environment.dirExists(home_dir, io, XDG_CONFIG_HOME_DIR_NAME)) {
break :lbl default_config;
}
- const dir = try home_dir.openDir(XDG_CONFIG_HOME_DIR_NAME, .{ .iterate = true });
- if (!environment.fileExists(dir, CONFIG_NAME)) {
+ const dir = try home_dir.openDir(io, XDG_CONFIG_HOME_DIR_NAME, .{ .iterate = true });
+ if (!environment.fileExists(dir, io, CONFIG_NAME)) {
break :lbl default_config;
}
break :dir dir;
}
- if (try environment.getHomeDir()) |home_dir| {
+ if (try environment.getHomeDir(io, init.environ_map)) |home_dir| {
defer {
var dir = home_dir;
- dir.close();
+ dir.close(io);
}
- if (!environment.dirExists(home_dir, HOME_DIR_NAME)) {
+ if (!environment.dirExists(home_dir, io, HOME_DIR_NAME)) {
break :lbl default_config;
}
- const dir = try home_dir.openDir(HOME_DIR_NAME, .{ .iterate = true });
- if (!environment.fileExists(dir, CONFIG_NAME)) {
+ const dir = try home_dir.openDir(io, HOME_DIR_NAME, .{ .iterate = true });
+ if (!environment.fileExists(dir, io, CONFIG_NAME)) {
break :lbl default_config;
}
@@ -96,33 +97,35 @@ pub fn main() !void {
break :lbl default_config;
};
- const contents = config_dir.readFileAlloc(alloc, CONFIG_NAME, 4096) catch break :lbl default_config;
- defer alloc.free(contents);
- const contentsZ = try alloc.dupeZ(u8, contents);
- defer alloc.free(contentsZ);
- break :lbl ziggy.parseLeaky(Config, alloc, contentsZ, .{}) catch default_config;
+ const contents = config_dir.readFileAlloc(io, CONFIG_NAME, allocator, .limited(4096)) catch break :lbl default_config;
+ defer allocator.free(contents);
+ const contentsZ = try allocator.dupeZ(u8, contents);
+ defer allocator.free(contentsZ);
+
+ var meta: ziggy.Deserializer.Meta = undefined;
+ break :lbl ziggy.deserializeLeaky(Config, allocator, contentsZ, &meta, .{}) catch default_config;
};
// Print header
- const user_env = try sys_info.getUser(alloc);
- defer alloc.free(user_env);
- const distro_id = try sys_info.getDistroId(alloc);
- defer alloc.free(distro_id);
+ const user_env = try sys_info.getUser(allocator, init.environ_map);
+ defer allocator.free(user_env);
+ const distro_id = try sys_info.getDistroId(io, allocator);
+ defer allocator.free(distro_id);
var header_buf: [1024]u8 = undefined;
const header = try std.fmt.bufPrint(&header_buf, "{s}@{s}\n", .{ user_env, distro_id });
- try stdout.writeAll(header);
- try stdout.print("{[str]s:-<[count]}\n", .{ .str = "-", .count = header.len + 1 });
+ try stdout_writer.writeAll(header);
+ try stdout_writer.print("{[str]s:-<[count]}\n", .{ .str = "-", .count = header.len + 1 });
// Print widgets
for (config.widgets) |widget| {
switch (widget) {
.distro => {
- const distro = sys_info.getDistro(alloc) catch continue;
- defer alloc.free(distro);
+ const distro = sys_info.getDistro(io, allocator) catch continue;
+ defer allocator.free(distro);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "distro", .stat = distro, .padding = padding },
);
@@ -130,7 +133,7 @@ pub fn main() !void {
.uptime => {
const uptime = stats.getUptime() catch continue;
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[days]d}d {[hours]d}h {[minutes]d}m\n",
.{
.header = "uptime",
@@ -145,61 +148,61 @@ pub fn main() !void {
const utsname = std.posix.uname();
const release = std.mem.sliceTo(&utsname.release, 0);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "kernel", .stat = release, .padding = padding },
);
},
.desktop => {
- const desktop = sys_info.getDesktop(alloc) catch continue;
- defer alloc.free(desktop);
+ const desktop = try sys_info.getDesktop(allocator, init.environ_map);
+ defer allocator.free(desktop);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "desktop", .stat = desktop, .padding = padding },
);
},
.shell => {
- const shell = sys_info.getShell(alloc) catch continue;
- defer alloc.free(shell);
+ const shell = sys_info.getShell(io, allocator, init.environ_map) catch continue;
+ defer allocator.free(shell);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "shell", .stat = shell, .padding = padding },
);
},
.memory => {
- const mem = stats.getMemory(alloc, .{ .mb = true }) catch continue;
- defer alloc.free(mem);
+ const mem = stats.getMemory(io, allocator, .{ .mb = true }) catch continue;
+ defer allocator.free(mem);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "memory", .stat = mem, .padding = padding },
);
},
.battery => {
- const battery = stats.getBattery(alloc) catch continue;
- defer alloc.free(battery);
+ const battery = stats.getBattery(io, allocator) catch continue;
+ defer allocator.free(battery);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "battery", .stat = battery, .padding = padding },
);
},
.cpu => {
- const cpu = sys_info.getCpu(alloc) catch continue;
- defer alloc.free(cpu);
+ const cpu = sys_info.getCpu(io, allocator) catch continue;
+ defer allocator.free(cpu);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "cpu", .stat = cpu, .padding = padding },
);
},
.disk => {
- const disk = sys_info.getDisk(alloc) catch continue;
- defer alloc.free(disk);
+ const disk = sys_info.getDisk(io, allocator) catch continue;
+ defer allocator.free(disk);
- try stdout.print(
+ try stdout_writer.print(
"{[header]s: <[padding]}{[stat]s}\n",
.{ .header = "disk", .stat = disk, .padding = padding },
);
@@ -207,5 +210,5 @@ pub fn main() !void {
}
}
- try stdout.flush();
+ try stdout_writer.flush();
}
diff --git a/src/process.zig b/src/process.zig
@@ -6,40 +6,39 @@ pub const ProcessInfo = struct {
ppid: std.c.pid_t,
};
-pub fn getProcessInfo(alloc: std.mem.Allocator, pid: std.c.pid_t) !ProcessInfo {
- switch (builtin.os.tag) {
- .linux, .openbsd => {
- var path_buf: [std.fs.max_path_bytes]u8 = undefined;
- const path = try std.fmt.bufPrint(&path_buf, "/proc/{d}/stat", .{pid});
+pub fn getProcessInfo(io: std.Io, allocator: std.mem.Allocator, pid: std.c.pid_t) !ProcessInfo {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
- const file = try std.fs.openFileAbsolute(path, .{ .mode = .read_only });
- defer file.close();
+ var path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, "/proc/{d}/stat", .{pid});
- var file_buffer: [4096]u8 = undefined;
- const bytes_read = try file.readAll(&file_buffer);
- const content = file_buffer[0..bytes_read];
+ const file = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_only });
+ defer file.close(io);
- const name_start = std.mem.indexOf(u8, content, "(") orelse return error.InvalidStatFormat;
- const name_end = std.mem.lastIndexOf(u8, content, ")") orelse return error.InvalidStatFormat;
+ var file_buffer: [4096]u8 = undefined;
+ var file_reader = file.reader(io, &.{});
- if (name_end <= name_start + 1) return error.InvalidStatFormat;
+ const bytes_read = try file_reader.interface.readSliceShort(&file_buffer);
+ const content = file_buffer[0..bytes_read];
- const name = try alloc.dupe(u8, content[name_start + 1 .. name_end]);
+ const name_start = std.mem.indexOf(u8, content, "(") orelse return error.InvalidStatFormat;
+ const name_end = std.mem.lastIndexOf(u8, content, ")") orelse return error.InvalidStatFormat;
- const after_name = content[name_end + 1 ..];
- var field_it = std.mem.tokenizeAny(u8, after_name, " \t\n");
+ if (name_end <= name_start + 1) return error.InvalidStatFormat;
- // Skip state field
- _ = field_it.next() orelse return error.InvalidStatFormat;
+ const name = try allocator.dupe(u8, content[name_start + 1 .. name_end]);
- const ppid_str = field_it.next() orelse return error.InvalidStatFormat;
- const ppid = try std.fmt.parseInt(std.c.pid_t, ppid_str, 10);
+ const after_name = content[name_end + 1 ..];
+ var field_it = std.mem.tokenizeAny(u8, after_name, " \t\n");
- return ProcessInfo{
- .name = name,
- .ppid = ppid,
- };
- },
- else => return error.UnsupportedOs,
- }
+ // Skip state field
+ _ = field_it.next() orelse return error.InvalidStatFormat;
+
+ const ppid_str = field_it.next() orelse return error.InvalidStatFormat;
+ const ppid = try std.fmt.parseInt(std.c.pid_t, ppid_str, 10);
+
+ return ProcessInfo{
+ .name = name,
+ .ppid = ppid,
+ };
}
diff --git a/src/stats.zig b/src/stats.zig
@@ -1,143 +1,144 @@
const std = @import("std");
const builtin = @import("builtin");
-pub fn getBattery(alloc: std.mem.Allocator) ![]const u8 {
- switch (builtin.os.tag) {
- .linux, .openbsd => {
- var dir = try std.fs.openDirAbsolute("/sys/class/power_supply/", .{ .iterate = true });
- defer dir.close();
-
- var smallest_battery: ?usize = null;
-
- // Walk dir and find the smallest battery.
- var it = dir.iterate();
- while (try it.next()) |entry| {
- if (std.mem.startsWith(u8, entry.name, "BAT")) {
- const battery_no = try std.fmt.parseInt(usize, std.mem.trimLeft(u8, entry.name, "BAT"), 10);
- if (smallest_battery) |smallest| {
- if (battery_no < smallest) smallest_battery = battery_no;
- } else {
- smallest_battery = battery_no;
- }
- }
+pub fn getBattery(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
+
+ var dir = try std.Io.Dir.openDirAbsolute(io, "/sys/class/power_supply/", .{ .iterate = true });
+ defer dir.close(io);
+
+ var smallest_battery: ?usize = null;
+
+ // Walk dir and find the smallest battery.
+ var it = try dir.walk(allocator);
+ defer it.deinit();
+ while (try it.next(io)) |entry| {
+ if (std.mem.startsWith(u8, entry.basename, "BAT")) {
+ const battery_no = try std.fmt.parseInt(usize, std.mem.trimStart(u8, entry.basename, "BAT"), 10);
+ if (smallest_battery) |smallest| {
+ if (battery_no < smallest) smallest_battery = battery_no;
+ } else {
+ smallest_battery = battery_no;
}
-
- if (smallest_battery == null) return error.NoBatteryDetected;
-
- // Read battery capacity.
- var battery_capacity_path_buf: [std.fs.max_path_bytes]u8 = undefined;
- const battery_capacity_path = try std.fmt.bufPrint(
- &battery_capacity_path_buf,
- "/sys/class/power_supply/BAT{d}/capacity",
- .{smallest_battery.?},
- );
- const battery_capacity_file = try std.fs.openFileAbsolute(
- battery_capacity_path,
- .{ .mode = .read_only },
- );
- defer battery_capacity_file.close();
-
- var battery_capacity_buf: [1024]u8 = undefined;
- const battery_capacity_bytes = try battery_capacity_file.readAll(&battery_capacity_buf);
- const battery_capacity = try std.fmt.parseInt(
- usize,
- std.mem.trim(u8, battery_capacity_buf[0..battery_capacity_bytes], "\n"),
- 10,
- );
-
- // Read battery status.
- var battery_status_path_buf: [std.fs.max_path_bytes]u8 = undefined;
- const battery_status_path = try std.fmt.bufPrint(
- &battery_status_path_buf,
- "/sys/class/power_supply/BAT{d}/status",
- .{smallest_battery.?},
- );
- const battery_status_file = try std.fs.openFileAbsolute(
- battery_status_path,
- .{ .mode = .read_only },
- );
- defer battery_status_file.close();
-
- var battery_status_buf: [1024]u8 = undefined;
- const battery_status_bytes = try battery_status_file.readAll(&battery_status_buf);
- const battery_status = std.mem.trim(
- u8,
- battery_status_buf[0..battery_status_bytes],
- "\n",
- );
-
- var output_buf: [1024]u8 = undefined;
- const output = try std.fmt.bufPrint(
- &output_buf,
- "{d}% ({s})",
- .{ battery_capacity, battery_status },
- );
- return try alloc.dupe(u8, output);
- },
- else => return error.UnsupportedOs,
+ }
}
-}
-pub fn getMemory(alloc: std.mem.Allocator, options: struct { mb: bool }) ![]const u8 {
- switch (builtin.os.tag) {
- .linux, .openbsd => {
- const file = try std.fs.openFileAbsolute("/proc/meminfo", .{ .mode = .read_only });
- defer file.close();
-
- var file_buffer: [1024]u8 = undefined;
- var file_reader = file.reader(&file_buffer);
-
- const dividend: usize = if (options.mb) 1000 else 1024;
- const suffix = if (options.mb) "MB" else "MiB";
-
- const mem_total = lbl: {
- const line = try file_reader.interface.takeDelimiter('\n') orelse return error.MemAvailableIsNotSpecified;
- var line_it = std.mem.splitScalar(u8, line, ':');
- _ = line_it.next(); // Skip "MemTotal:"
- const mem_total_with_suffix = line_it.next() orelse return error.MemTotalIsNull;
- // TODO: Is it safe to assume the suffix will only ever be "kB"?
- const mem_total_str = std.mem.trim(u8, mem_total_with_suffix, " \tkB");
- const mem_total_int = (try std.fmt.parseInt(usize, mem_total_str, 10)) / dividend;
- break :lbl mem_total_int;
- };
-
- _ = try file_reader.interface.takeDelimiter('\n'); // Skip "MemFree: x"
-
- const mem_available = lbl: {
- const line = try file_reader.interface.takeDelimiter('\n') orelse return error.MemAvailableIsNotSpecified;
- var line_it = std.mem.splitScalar(u8, line, ':');
- _ = line_it.next(); // Skip "MemAvailable:"
- const mem_available_with_suffix = line_it.next() orelse return error.MemAvailableIsNull;
- // TODO: Is it safe to assume it'll only ever be "kB"?
- const mem_available_str = std.mem.trim(u8, mem_available_with_suffix, " \tkB");
- const mem_available_int = (try std.fmt.parseInt(usize, mem_available_str, 10)) / dividend;
- break :lbl mem_available_int;
- };
+ if (smallest_battery == null) return error.NoBatteryDetected;
+
+ // Read battery capacity.
+ var battery_capacity_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
+ const battery_capacity_path = try std.fmt.bufPrint(
+ &battery_capacity_path_buf,
+ "/sys/class/power_supply/BAT{d}/capacity",
+ .{smallest_battery.?},
+ );
+ const battery_capacity_file = try std.Io.Dir.openFileAbsolute(
+ io,
+ battery_capacity_path,
+ .{ .mode = .read_only },
+ );
+ defer battery_capacity_file.close(io);
+
+ var battery_capacity_buf: [1024]u8 = undefined;
+ var battery_capacity_file_reader = battery_capacity_file.reader(io, &.{});
+
+ const battery_capacity_bytes = try battery_capacity_file_reader.interface.readSliceShort(&battery_capacity_buf);
+ const battery_capacity = try std.fmt.parseInt(
+ usize,
+ std.mem.trim(u8, battery_capacity_buf[0..battery_capacity_bytes], "\n"),
+ 10,
+ );
+
+ // Read battery status.
+ var battery_status_path_buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
+ const battery_status_path = try std.fmt.bufPrint(
+ &battery_status_path_buf,
+ "/sys/class/power_supply/BAT{d}/status",
+ .{smallest_battery.?},
+ );
+ const battery_status_file = try std.Io.Dir.openFileAbsolute(
+ io,
+ battery_status_path,
+ .{ .mode = .read_only },
+ );
+ defer battery_status_file.close(io);
+
+ var battery_status_buf: [1024]u8 = undefined;
+ var battery_status_file_reader = battery_status_file.reader(io, &.{});
+
+ const battery_status_bytes = try battery_status_file_reader.interface.readSliceShort(&battery_status_buf);
+ const battery_status = std.mem.trim(
+ u8,
+ battery_status_buf[0..battery_status_bytes],
+ "\n",
+ );
+
+ var output_buf: [1024]u8 = undefined;
+ const output = try std.fmt.bufPrint(
+ &output_buf,
+ "{d}% ({s})",
+ .{ battery_capacity, battery_status },
+ );
+ return try allocator.dupe(u8, output);
+}
- const mem_used = mem_total - mem_available;
- const mem_percentage: usize = @intFromFloat(
- (@as(f32, @floatFromInt(mem_used)) / @as(f32, @floatFromInt(mem_total))) * 100,
- );
-
- var buf: [1024]u8 = undefined;
- const mem_str = try std.fmt.bufPrint(&buf, "{d} / {d} {s} ({d}%)", .{
- mem_used,
- mem_total,
- suffix,
- mem_percentage,
- });
- return try alloc.dupe(u8, mem_str);
- },
- else => return error.UnsupportedOs,
- }
+pub fn getMemory(io: std.Io, allocator: std.mem.Allocator, options: struct { mb: bool }) ![]const u8 {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
+
+ const file = try std.Io.Dir.openFileAbsolute(io, "/proc/meminfo", .{ .mode = .read_only });
+ defer file.close(io);
+
+ var file_buffer: [1024]u8 = undefined;
+ var file_reader = file.reader(io, &file_buffer);
+
+ const dividend: usize = if (options.mb) 1000 else 1024;
+ const suffix = if (options.mb) "MB" else "MiB";
+
+ const mem_total = lbl: {
+ const line = try file_reader.interface.takeDelimiter('\n') orelse return error.MemAvailableIsNotSpecified;
+ var line_it = std.mem.splitScalar(u8, line, ':');
+ _ = line_it.next(); // Skip "MemTotal:"
+ const mem_total_with_suffix = line_it.next() orelse return error.MemTotalIsNull;
+ // TODO: Is it safe to assume the suffix will only ever be "kB"?
+ const mem_total_str = std.mem.trim(u8, mem_total_with_suffix, " \tkB");
+ const mem_total_int = (try std.fmt.parseInt(usize, mem_total_str, 10)) / dividend;
+ break :lbl mem_total_int;
+ };
+
+ _ = try file_reader.interface.takeDelimiter('\n'); // Skip "MemFree: x"
+
+ const mem_available = lbl: {
+ const line = try file_reader.interface.takeDelimiter('\n') orelse return error.MemAvailableIsNotSpecified;
+ var line_it = std.mem.splitScalar(u8, line, ':');
+ _ = line_it.next(); // Skip "MemAvailable:"
+ const mem_available_with_suffix = line_it.next() orelse return error.MemAvailableIsNull;
+ // TODO: Is it safe to assume it'll only ever be "kB"?
+ const mem_available_str = std.mem.trim(u8, mem_available_with_suffix, " \tkB");
+ const mem_available_int = (try std.fmt.parseInt(usize, mem_available_str, 10)) / dividend;
+ break :lbl mem_available_int;
+ };
+
+ const mem_used = mem_total - mem_available;
+ const mem_percentage: usize = @intFromFloat(
+ (@as(f32, @floatFromInt(mem_used)) / @as(f32, @floatFromInt(mem_total))) * 100,
+ );
+
+ var buf: [1024]u8 = undefined;
+ const mem_str = try std.fmt.bufPrint(&buf, "{d} / {d} {s} ({d}%)", .{
+ mem_used,
+ mem_total,
+ suffix,
+ mem_percentage,
+ });
+ return try allocator.dupe(u8, mem_str);
}
pub fn getUptime() !struct { days: isize, minutes: isize, hours: isize } {
switch (builtin.os.tag) {
.linux, .openbsd => {
var info: std.os.linux.Sysinfo = undefined;
- const result: usize = std.os.linux.sysinfo(&info);
- if (std.os.linux.E.init(result) != .SUCCESS) {
+ const result: std.os.linux.E = @enumFromInt(std.os.linux.sysinfo(&info));
+ if (result != .SUCCESS) {
return error.UnknownUptime;
}
const uptime = info.uptime;
@@ -152,32 +153,33 @@ pub fn getUptime() !struct { days: isize, minutes: isize, hours: isize } {
.hours = uptime_hours,
};
},
- .macos => {
- var boot_timeval: std.posix.timeval = undefined;
- var size: usize = @sizeOf(@TypeOf(boot_timeval));
- const kern_boottime = [_]c_int{ 1, 21 }; // CTL_KERN, KERN_BOOTTIME return void on macos
- std.posix.sysctl(
- &kern_boottime,
- @ptrCast(&boot_timeval),
- &size,
- null,
- 0,
- ) catch return error.SysctlFailed;
-
- const now = std.time.timestamp();
- const boot_time = boot_timeval.sec;
- const uptime = now - boot_time;
-
- const uptime_days = @divTrunc(uptime, 86400);
- const uptime_hours = @divTrunc(@rem(uptime, 86400), 3600);
- const uptime_minutes = @divTrunc(@rem(uptime, 3600), 60);
-
- return .{
- .days = uptime_days,
- .minutes = uptime_minutes,
- .hours = uptime_hours,
- };
- },
+ // TODO: upgrade to 0.16.0
+ // .macos => {
+ // var boot_timeval: std.posix.timeval = undefined;
+ // var size: usize = @sizeOf(@TypeOf(boot_timeval));
+ // const kern_boottime = [_]c_int{ 1, 21 }; // CTL_KERN, KERN_BOOTTIME return void on macos
+ // std.posix.sysctl(
+ // &kern_boottime,
+ // @ptrCast(&boot_timeval),
+ // &size,
+ // null,
+ // 0,
+ // ) catch return error.SysctlFailed;
+ //
+ // const now = std.time.timestamp();
+ // const boot_time = boot_timeval.sec;
+ // const uptime = now - boot_time;
+ //
+ // const uptime_days = @divTrunc(uptime, 86400);
+ // const uptime_hours = @divTrunc(@rem(uptime, 86400), 3600);
+ // const uptime_minutes = @divTrunc(@rem(uptime, 3600), 60);
+ //
+ // return .{
+ // .days = uptime_days,
+ // .minutes = uptime_minutes,
+ // .hours = uptime_hours,
+ // };
+ // },
else => return error.UnsupportedOs,
}
}
diff --git a/src/sys_info.zig b/src/sys_info.zig
@@ -3,17 +3,18 @@ const builtin = @import("builtin");
const process = @import("process.zig");
-pub fn getCpu(alloc: std.mem.Allocator) ![]const u8 {
+pub fn getCpu(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
switch (builtin.os.tag) {
.linux, .openbsd => {
- const file = try std.fs.openFileAbsolute(
+ const file = try std.Io.Dir.openFileAbsolute(
+ io,
"/proc/cpuinfo",
.{ .mode = .read_only },
);
- defer file.close();
+ defer file.close(io);
var file_buffer: [1024]u8 = undefined;
- var file_reader = file.reader(&file_buffer);
+ var file_reader = file.reader(io, &file_buffer);
while (try file_reader.interface.takeDelimiter('\n')) |line| {
if (std.mem.startsWith(u8, line, "model name")) {
@@ -21,32 +22,32 @@ pub fn getCpu(alloc: std.mem.Allocator) ![]const u8 {
_ = name_it.next(); // Skip "model name :"
const name = name_it.next() orelse return error.ModelNameIsNull;
- return try alloc.dupe(u8, std.mem.trim(u8, name, " \t"));
+ return try allocator.dupe(u8, std.mem.trim(u8, name, " \t"));
}
}
return error.ModelNameIsNotSpecified;
},
- .macos => {
- const child = try std.process.Child.run(.{
- .allocator = alloc,
- .argv = &[_][]const u8{ "sysctl", "-n", "machdep.cpu.brand_string" },
- });
- defer alloc.free(child.stderr);
-
- if (child.term.Exited != 0) {
- return error.FailedToReadMacCPU;
- }
-
- return child.stdout;
- },
+ // TODO: upgrade to 0.16.0
+ // .macos => {
+ // const child = try std.process.Child.run(.{
+ // .allocator = allocator,
+ // .argv = &[_][]const u8{ "sysctl", "-n", "machdep.cpu.brand_string" },
+ // });
+ // defer allocator.free(child.stderr);
+ //
+ // if (child.term.Exited != 0) {
+ // return error.FailedToReadMacCPU;
+ // }
+ //
+ // return child.stdout;
+ // },
else => return error.UnsupportedOs,
}
}
-pub fn getShell(alloc: std.mem.Allocator) ![]const u8 {
- var env_map = try std.process.getEnvMap(alloc);
- defer env_map.deinit();
+pub fn getShell(io: std.Io, allocator: std.mem.Allocator, env_map: *std.process.Environ.Map) ![]const u8 {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
if (env_map.get("SHELL")) |shell_path| {
var shell_it = std.mem.tokenizeScalar(u8, shell_path, '/');
@@ -56,61 +57,56 @@ pub fn getShell(alloc: std.mem.Allocator) ![]const u8 {
shell = split;
}
- return try alloc.dupe(u8, shell);
+ return try allocator.dupe(u8, shell);
}
// If $SHELL is not set, try find the shell via pid.
// Walk up the process tree: current -> parent -> grandparent
- const parent_info = try process.getProcessInfo(alloc, std.os.linux.getppid());
- defer alloc.free(parent_info.name);
+ const parent_info = try process.getProcessInfo(io, allocator, std.os.linux.getppid());
+ defer allocator.free(parent_info.name);
- const grandparent_info = try process.getProcessInfo(alloc, parent_info.ppid);
+ const grandparent_info = try process.getProcessInfo(io, allocator, parent_info.ppid);
return grandparent_info.name;
}
-pub fn getDesktop(alloc: std.mem.Allocator) ![]const u8 {
- switch (builtin.os.tag) {
- .linux, .openbsd => {
- var env_map = try std.process.getEnvMap(alloc);
- defer env_map.deinit();
-
- if (env_map.get("XDG_CURRENT_DESKTOP")) |desktop| {
- return try alloc.dupe(u8, desktop);
- }
+pub fn getDesktop(allocator: std.mem.Allocator, env_map: *std.process.Environ.Map) ![]const u8 {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
- if (env_map.get("XDG_SESSION_DESKTOP")) |desktop| {
- return try alloc.dupe(u8, desktop);
- }
+ if (env_map.get("XDG_CURRENT_DESKTOP")) |desktop| {
+ return try allocator.dupe(u8, desktop);
+ }
- if (env_map.get("DESKTOP_SESSION")) |desktop| {
- return try alloc.dupe(u8, desktop);
- }
+ if (env_map.get("XDG_SESSION_DESKTOP")) |desktop| {
+ return try allocator.dupe(u8, desktop);
+ }
- if (env_map.get("XDG_SESSION_TYPE")) |session_type| {
- if (std.mem.eql(u8, session_type, "tty")) {
- return try alloc.dupe(u8, "Headless");
- }
- }
+ if (env_map.get("DESKTOP_SESSION")) |desktop| {
+ return try allocator.dupe(u8, desktop);
+ }
- return try alloc.dupe(u8, "Unknown");
- },
- else => return error.UnsupportedOs,
+ if (env_map.get("XDG_SESSION_TYPE")) |session_type| {
+ if (std.mem.eql(u8, session_type, "tty")) {
+ return try allocator.dupe(u8, "Headless");
+ }
}
+
+ return try allocator.dupe(u8, "Unknown");
}
-pub fn getDistro(alloc: std.mem.Allocator) ![]const u8 {
+pub fn getDistro(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
switch (builtin.os.tag) {
.linux, .openbsd => {
const utsname = std.posix.uname();
- const file = try std.fs.openFileAbsolute(
+ const file = try std.Io.Dir.openFileAbsolute(
+ io,
"/etc/os-release",
.{ .mode = .read_only },
);
- defer file.close();
+ defer file.close(io);
var file_buffer: [1024]u8 = undefined;
- var file_reader = file.reader(&file_buffer);
+ var file_reader = file.reader(io, &file_buffer);
while (try file_reader.interface.takeDelimiter('\n')) |line| {
if (std.mem.startsWith(u8, line, "PRETTY_NAME")) {
@@ -124,7 +120,7 @@ pub fn getDistro(alloc: std.mem.Allocator) ![]const u8 {
"{s} {s}",
.{ std.mem.trim(u8, name, "\""), std.mem.sliceTo(&utsname.machine, 0) },
);
- return try alloc.dupe(u8, name_machine);
+ return try allocator.dupe(u8, name_machine);
}
}
@@ -139,30 +135,31 @@ pub fn getDistro(alloc: std.mem.Allocator) ![]const u8 {
"MacOS X {s}",
.{std.mem.sliceTo(&utsname.machine, 0)},
);
- return try alloc.dupe(u8, name_machine);
+ return try allocator.dupe(u8, name_machine);
},
else => return error.UnsupportedOs,
}
}
-pub fn getDistroId(alloc: std.mem.Allocator) ![]const u8 {
+pub fn getDistroId(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
switch (builtin.os.tag) {
.linux, .openbsd => {
- const file = try std.fs.openFileAbsolute(
+ const file = try std.Io.Dir.openFileAbsolute(
+ io,
"/etc/os-release",
.{ .mode = .read_only },
);
- defer file.close();
+ defer file.close(io);
var file_buffer: [1024]u8 = undefined;
- var file_reader = file.reader(&file_buffer);
+ var file_reader = file.reader(io, &file_buffer);
while (try file_reader.interface.takeDelimiter('\n')) |line| {
if (std.mem.startsWith(u8, line, "ID")) {
var id_it = std.mem.tokenizeScalar(u8, line, '=');
_ = id_it.next(); // Skip "ID="
const id = id_it.next() orelse return error.OsIdIsNull;
- return try alloc.dupe(u8, id);
+ return try allocator.dupe(u8, id);
}
}
@@ -170,69 +167,62 @@ pub fn getDistroId(alloc: std.mem.Allocator) ![]const u8 {
},
.macos => {
const utsname = std.posix.uname();
- return try alloc.dupe(u8, std.mem.sliceTo(&utsname.nodename, 0));
+ return try allocator.dupe(u8, std.mem.sliceTo(&utsname.nodename, 0));
},
else => return error.UnsupportedOs,
}
}
-pub fn getUser(alloc: std.mem.Allocator) ![]const u8 {
- var env_map = try std.process.getEnvMap(alloc);
- defer env_map.deinit();
-
+pub fn getUser(allocator: std.mem.Allocator, env_map: *std.process.Environ.Map) ![]const u8 {
const user_env = env_map.get("USER") orelse return error.USEREnvNotSet;
- return try alloc.dupe(u8, user_env);
+ return try allocator.dupe(u8, user_env);
}
-pub fn getDisk(alloc: std.mem.Allocator) ![]const u8 {
- switch (builtin.os.tag) {
- .linux, .openbsd => {
- const child = try std.process.Child.run(.{
- .allocator = alloc,
- .argv = &[_][]const u8{ "df", "-h", "--output=size,used,pcent", "/" },
- });
- defer alloc.free(child.stdout);
- defer alloc.free(child.stderr);
-
- if (child.term.Exited != 0) {
- return error.FailedToReadMacCPU;
- }
-
- var output_it = std.mem.tokenizeScalar(u8, child.stdout, '\n');
- _ = output_it.next(); // Skip headers.
-
- const line = std.mem.trim(
- u8,
- output_it.next() orelse return error.DiskUsageIsNull,
- " ",
- );
+pub fn getDisk(io: std.Io, allocator: std.mem.Allocator) ![]const u8 {
+ if (builtin.os.tag != .linux and builtin.os.tag != .openbsd) return error.UnsupportedOs;
- var line_it = std.mem.tokenizeScalar(u8, line, ' ');
+ const child = try std.process.run(allocator, io, .{
+ .argv = &[_][]const u8{ "df", "-h", "--output=size,used,pcent", "/" },
+ });
+ defer allocator.free(child.stdout);
+ defer allocator.free(child.stderr);
- const total = std.mem.trim(
- u8,
- line_it.next() orelse return error.DiskTotalIsNull,
- " ",
- );
- const used = std.mem.trim(
- u8,
- line_it.next() orelse return error.DiskUsedIsNull,
- " ",
- );
- const percentage = std.mem.trim(
- u8,
- line_it.next() orelse return error.DiskPercentageIsNull,
- " ",
- );
-
- var output_buf: [1024]u8 = undefined;
- const output = try std.fmt.bufPrint(
- &output_buf,
- "{s} / {s} ({s})",
- .{ used, total, percentage },
- );
- return try alloc.dupe(u8, output);
- },
- else => return error.UnsupportedOs,
+ if (child.term.exited != 0) {
+ return error.FailedToReadMacCPU;
}
+
+ var output_it = std.mem.tokenizeScalar(u8, child.stdout, '\n');
+ _ = output_it.next(); // Skip headers.
+
+ const line = std.mem.trim(
+ u8,
+ output_it.next() orelse return error.DiskUsageIsNull,
+ " ",
+ );
+
+ var line_it = std.mem.tokenizeScalar(u8, line, ' ');
+
+ const total = std.mem.trim(
+ u8,
+ line_it.next() orelse return error.DiskTotalIsNull,
+ " ",
+ );
+ const used = std.mem.trim(
+ u8,
+ line_it.next() orelse return error.DiskUsedIsNull,
+ " ",
+ );
+ const percentage = std.mem.trim(
+ u8,
+ line_it.next() orelse return error.DiskPercentageIsNull,
+ " ",
+ );
+
+ var output_buf: [1024]u8 = undefined;
+ const output = try std.fmt.bufPrint(
+ &output_buf,
+ "{s} / {s} ({s})",
+ .{ used, total, percentage },
+ );
+ return try allocator.dupe(u8, output);
}