diff --git a/src/linux/hardware.zig b/src/linux/hardware.zig new file mode 100644 index 0000000..b1d3559 --- /dev/null +++ b/src/linux/hardware.zig @@ -0,0 +1,121 @@ +const std = @import("std"); +const c_unistd = @cImport(@cInclude("unistd.h")); + +pub const CpuInfo = struct { + cpu_name: []u8, + cpu_cores: i32, + cpu_max_freq: f32, +}; + +pub const RamInfo = struct { + ram_size: f64, + ram_usage: f64, + ram_usage_percentage: u8, +}; + +pub fn getCpuInfo(allocator: std.mem.Allocator) !CpuInfo { + const cpu_cores = c_unistd.sysconf(c_unistd._SC_NPROCESSORS_ONLN); + + // Reads /proc/cpuinfo + const cpuinfo_path = "/proc/cpuinfo"; + var file = try std.fs.cwd().openFile(cpuinfo_path, .{}); + defer file.close(); + const cpuinfo_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(cpuinfo_data); + + // Parsing /proc/cpuinfo + var model_name: ?[]const u8 = null; + + var lines = std.mem.split(u8, cpuinfo_data, "\n"); + while (lines.next()) |line| { + const trimmed = std.mem.trim(u8, line, " \t"); + if (std.mem.startsWith(u8, trimmed, "model name") and model_name == null) { + var parts = std.mem.split(u8, trimmed, ":"); + _ = parts.next(); // discards the key + if (parts.next()) |value| { + model_name = std.mem.trim(u8, value, " "); + break; + } + } + } + + // Reads /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq + const cpuinfo_max_freq_path = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"; + var file2 = try std.fs.cwd().openFile(cpuinfo_max_freq_path, .{}); + defer file2.close(); + const cpuinfo_max_freq_data = try file2.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(cpuinfo_max_freq_data); + + // Parsing /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq + const trimmed = std.mem.trim(u8, cpuinfo_max_freq_data, " \n\r"); + const cpu_max_freq_khz: f32 = try std.fmt.parseFloat(f32, trimmed); + const cpu_max_freq: f32 = cpu_max_freq_khz / 1_000_000; + + return CpuInfo{ + .cpu_name = try allocator.dupe(u8, model_name orelse "Unknown"), + .cpu_cores = @as(i32, @intCast(cpu_cores)), + .cpu_max_freq = cpu_max_freq, + }; +} + +pub fn getRamInfo(allocator: std.mem.Allocator) !RamInfo { + // Reads /proc/meminfo + const meminfo_path = "/proc/meminfo"; + const file = try std.fs.cwd().openFile(meminfo_path, .{}); + defer file.close(); + const meminfo_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(meminfo_data); + + // Parsing /proc/meminfo + var total_mem: f64 = 0.0; + var free_mem: f64 = 0.0; // remove? + var available_mem: f64 = 0.0; + + var total_mem_str: ?[]const u8 = null; + var free_mem_str: ?[]const u8 = null; + var available_mem_str: ?[]const u8 = null; + + var lines = std.mem.split(u8, meminfo_data, "\n"); + while (lines.next()) |line| { + const trimmed = std.mem.trim(u8, line, " \t"); + if (std.mem.startsWith(u8, trimmed, "MemTotal")) { + var parts = std.mem.split(u8, trimmed, ":"); + _ = parts.next(); // discards the key + if (parts.next()) |value| { + total_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); + total_mem = try std.fmt.parseFloat(f64, total_mem_str.?); + } + } else if (std.mem.startsWith(u8, trimmed, "MemFree")) { + var parts = std.mem.split(u8, trimmed, ":"); + _ = parts.next(); // discards the key + if (parts.next()) |value| { + free_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); + free_mem = try std.fmt.parseFloat(f64, free_mem_str.?); + } + } else if (std.mem.startsWith(u8, trimmed, "MemAvailable")) { + var parts = std.mem.split(u8, trimmed, ":"); + _ = parts.next(); // discards the key + if (parts.next()) |value| { + available_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); + available_mem = try std.fmt.parseFloat(f64, available_mem_str.?); + } + } + + if ((total_mem_str != null) and (free_mem_str != null) and (available_mem_str != null)) { + break; + } + } + + var used_mem = total_mem - available_mem; + + // Converts KB in GB + total_mem /= (1024 * 1024); + used_mem /= (1024 * 1024); + const ram_usage_percentage: u8 = @as(u8, @intFromFloat((used_mem * 100) / total_mem)); + + return RamInfo{ + .ram_size = total_mem, + .ram_usage = used_mem, + .ram_usage_percentage = ram_usage_percentage, + }; +} diff --git a/src/linux/linux.zig b/src/linux/linux.zig index 86eaacc..f7ee652 100644 --- a/src/linux/linux.zig +++ b/src/linux/linux.zig @@ -1,283 +1,4 @@ -const std = @import("std"); -const c_sysinfo = @cImport(@cInclude("sys/sysinfo.h")); -const c_unistd = @cImport(@cInclude("unistd.h")); -const c_utsname = @cImport(@cInclude("sys/utsname.h")); -const c_ifaddrs = @cImport(@cInclude("ifaddrs.h")); -const c_inet = @cImport(@cInclude("arpa/inet.h")); -const c_net_if = @cImport(@cInclude("net/if.h")); -const c_netinet_in = @cImport(@cInclude("netinet/in.h")); -const c_socket = @cImport(@cInclude("sys/socket.h")); - -/// Structure representing system uptime in days, hours, and minutes. -pub const SystemUptime = struct { - days: i8, - hours: i8, - minutes: i8, -}; - -pub const CpuInfo = struct { - cpu_name: []u8, - cpu_cores: i32, - cpu_max_freq: f32, -}; - -pub const RamInfo = struct { - ram_size: f64, - ram_usage: f64, - ram_usage_percentage: u8, -}; - -pub const KernelInfo = struct { - kernel_name: []u8, - kernel_release: []u8, -}; - -pub const NetInfo = struct { - interface_name: []u8, - ipv4_addr: []u8, -}; - -pub fn getUsername(allocator: std.mem.Allocator) ![]u8 { - const username = try std.process.getEnvVarOwned(allocator, "USER"); - return username; -} - -pub fn getHostname(allocator: std.mem.Allocator) ![]u8 { - var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; - const hostnameEnv = try std.posix.gethostname(&buf); - - const hostname = try allocator.dupe(u8, hostnameEnv); - - return hostname; -} - -/// Returns the system uptime. -/// -/// Uses `sysinfo` to fetch the system uptime and calculates the elapsed time. -pub fn getSystemUptime() !SystemUptime { - const seconds_per_day: f64 = 86400.0; - const hours_per_day: f64 = 24.0; - const seconds_per_hour: f64 = 3600.0; - const seconds_per_minute: f64 = 60.0; - - var info: c_sysinfo.struct_sysinfo = undefined; - if (c_sysinfo.sysinfo(&info) != 0) { - return error.SysinfoFailed; - } - - const uptime_seconds: f64 = @as(f64, @floatFromInt(info.uptime)); - - var remainig_seconds: f64 = uptime_seconds; - const days: f64 = @floor(remainig_seconds / seconds_per_day); - - remainig_seconds = (remainig_seconds / seconds_per_day) - days; - const hours = @floor(remainig_seconds * hours_per_day); - - remainig_seconds = (remainig_seconds * hours_per_day) - hours; - const minutes = @floor((remainig_seconds * seconds_per_hour) / seconds_per_minute); - - return SystemUptime{ - .days = @as(i8, @intFromFloat(days)), - .hours = @as(i8, @intFromFloat(hours)), - .minutes = @as(i8, @intFromFloat(minutes)), - }; -} - -pub fn getShell(allocator: std.mem.Allocator) ![]u8 { - const shell = try std.process.getEnvVarOwned(allocator, "SHELL"); - - var child = std.process.Child.init(&[_][]const u8{ shell, "--version" }, allocator); - - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const output = try child.stdout.?.reader().readAllAlloc(allocator, 1024 * 1024); - - _ = try child.wait(); - - return output; -} - -pub fn getCpuInfo(allocator: std.mem.Allocator) !CpuInfo { - const cpu_cores = c_unistd.sysconf(c_unistd._SC_NPROCESSORS_ONLN); - - // Reads /proc/cpuinfo - const cpuinfo_path = "/proc/cpuinfo"; - var file = try std.fs.cwd().openFile(cpuinfo_path, .{}); - defer file.close(); - const cpuinfo_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); - defer allocator.free(cpuinfo_data); - - // Parsing /proc/cpuinfo - var model_name: ?[]const u8 = null; - - var lines = std.mem.split(u8, cpuinfo_data, "\n"); - while (lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, " \t"); - if (std.mem.startsWith(u8, trimmed, "model name") and model_name == null) { - var parts = std.mem.split(u8, trimmed, ":"); - _ = parts.next(); // discards the key - if (parts.next()) |value| { - model_name = std.mem.trim(u8, value, " "); - break; - } - } - } - - // Reads /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq - const cpuinfo_max_freq_path = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"; - var file2 = try std.fs.cwd().openFile(cpuinfo_max_freq_path, .{}); - defer file2.close(); - const cpuinfo_max_freq_data = try file2.readToEndAlloc(allocator, std.math.maxInt(usize)); - defer allocator.free(cpuinfo_max_freq_data); - - // Parsing /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq - const trimmed = std.mem.trim(u8, cpuinfo_max_freq_data, " \n\r"); - const cpu_max_freq_khz: f32 = try std.fmt.parseFloat(f32, trimmed); - const cpu_max_freq: f32 = cpu_max_freq_khz / 1_000_000; - - return CpuInfo{ - .cpu_name = try allocator.dupe(u8, model_name orelse "Unknown"), - .cpu_cores = @as(i32, @intCast(cpu_cores)), - .cpu_max_freq = cpu_max_freq, - }; -} - -pub fn getRamInfo(allocator: std.mem.Allocator) !RamInfo { - // Reads /proc/meminfo - const meminfo_path = "/proc/meminfo"; - const file = try std.fs.cwd().openFile(meminfo_path, .{}); - defer file.close(); - const meminfo_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); - defer allocator.free(meminfo_data); - - // Parsing /proc/meminfo - var total_mem: f64 = 0.0; - var free_mem: f64 = 0.0; // remove? - var available_mem: f64 = 0.0; - - var total_mem_str: ?[]const u8 = null; - var free_mem_str: ?[]const u8 = null; - var available_mem_str: ?[]const u8 = null; - - var lines = std.mem.split(u8, meminfo_data, "\n"); - while (lines.next()) |line| { - const trimmed = std.mem.trim(u8, line, " \t"); - if (std.mem.startsWith(u8, trimmed, "MemTotal")) { - var parts = std.mem.split(u8, trimmed, ":"); - _ = parts.next(); // discards the key - if (parts.next()) |value| { - total_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); - total_mem = try std.fmt.parseFloat(f64, total_mem_str.?); - } - } else if (std.mem.startsWith(u8, trimmed, "MemFree")) { - var parts = std.mem.split(u8, trimmed, ":"); - _ = parts.next(); // discards the key - if (parts.next()) |value| { - free_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); - free_mem = try std.fmt.parseFloat(f64, free_mem_str.?); - } - } else if (std.mem.startsWith(u8, trimmed, "MemAvailable")) { - var parts = std.mem.split(u8, trimmed, ":"); - _ = parts.next(); // discards the key - if (parts.next()) |value| { - available_mem_str = std.mem.trim(u8, value[0..(value.len - 3)], " "); - available_mem = try std.fmt.parseFloat(f64, available_mem_str.?); - } - } - - if ((total_mem_str != null) and (free_mem_str != null) and (available_mem_str != null)) { - break; - } - } - - var used_mem = total_mem - available_mem; - - // Converts KB in GB - total_mem /= (1024 * 1024); - used_mem /= (1024 * 1024); - const ram_usage_percentage: u8 = @as(u8, @intFromFloat((used_mem * 100) / total_mem)); - - return RamInfo{ - .ram_size = total_mem, - .ram_usage = used_mem, - .ram_usage_percentage = ram_usage_percentage, - }; -} - -pub fn getKernelInfo(allocator: std.mem.Allocator) !KernelInfo { - var uts: c_utsname.struct_utsname = undefined; - if (c_utsname.uname(&uts) != 0) { - return error.UnameFailed; - } - - return KernelInfo{ - .kernel_name = try allocator.dupe(u8, &uts.sysname), - .kernel_release = try allocator.dupe(u8, &uts.release), - }; -} - -pub fn getOsInfo(allocator: std.mem.Allocator) ![]u8 { - const os_release_path = "/etc/os-release"; - const file = try std.fs.cwd().openFile(os_release_path, .{}); - defer file.close(); - const os_release_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); - defer allocator.free(os_release_data); - - var pretty_name: ?[]const u8 = null; - - var lines = std.mem.split(u8, os_release_data, "\n"); - while (lines.next()) |line| { - if (std.mem.startsWith(u8, line, "PRETTY_NAME")) { - var parts = std.mem.split(u8, line, "="); - _ = parts.next(); // discard the key - if (parts.next()) |value| { - pretty_name = std.mem.trim(u8, value, "\""); - break; - } - } - } - - return try allocator.dupe(u8, pretty_name orelse "Unknown"); -} - -pub fn getTerminalName(allocator: std.mem.Allocator) ![]u8 { - const term_progrm = try std.process.getEnvVarOwned(allocator, "TERM_PROGRAM"); - return term_progrm; -} - -pub fn getNetInfo(allocator: std.mem.Allocator) !std.ArrayList(NetInfo) { - var net_info_list = std.ArrayList(NetInfo).init(allocator); - - var ifap: ?*c_ifaddrs.ifaddrs = null; - if (c_ifaddrs.getifaddrs(&ifap) != 0) { - return error.GetifaddrsFailed; - } - defer c_ifaddrs.freeifaddrs(ifap); - - var cur: ?*c_ifaddrs.ifaddrs = ifap; - while (cur) |ifa| : (cur = ifa.ifa_next) { - if (ifa.ifa_addr) |addr| { - // Skips the loopback - if ((ifa.ifa_flags & c_net_if.IFF_LOOPBACK) != 0) continue; - - const sockaddr_ptr = @as(*const c_socket.sockaddr, @ptrCast(@alignCast(addr))); - - if (sockaddr_ptr.sa_family != c_inet.AF_INET) continue; - - var addr_in = @as(*const c_netinet_in.sockaddr_in, @ptrCast(@alignCast(sockaddr_ptr))); - var ip_buf: [c_inet.INET_ADDRSTRLEN]u8 = undefined; - const ip_str = c_inet.inet_ntop(c_inet.AF_INET, &addr_in.sin_addr, &ip_buf, c_inet.INET_ADDRSTRLEN); - if (ip_str) |ip| { - try net_info_list.append(NetInfo{ - .interface_name = try allocator.dupe(u8, std.mem.span(ifa.ifa_name)), - .ipv4_addr = try allocator.dupe(u8, std.mem.span(ip)), - }); - } - } - } - - return net_info_list; -} +pub const hardware = @import("./hardware.zig"); +pub const network = @import("./network.zig"); +pub const system = @import("./system.zig"); +pub const user = @import("./user.zig"); diff --git a/src/linux/network.zig b/src/linux/network.zig new file mode 100644 index 0000000..b8da949 --- /dev/null +++ b/src/linux/network.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const c_ifaddrs = @cImport(@cInclude("ifaddrs.h")); +const c_inet = @cImport(@cInclude("arpa/inet.h")); +const c_net_if = @cImport(@cInclude("net/if.h")); +const c_netinet_in = @cImport(@cInclude("netinet/in.h")); +const c_socket = @cImport(@cInclude("sys/socket.h")); + +pub const NetInfo = struct { + interface_name: []u8, + ipv4_addr: []u8, +}; + +pub fn getNetInfo(allocator: std.mem.Allocator) !std.ArrayList(NetInfo) { + var net_info_list = std.ArrayList(NetInfo).init(allocator); + + var ifap: ?*c_ifaddrs.ifaddrs = null; + if (c_ifaddrs.getifaddrs(&ifap) != 0) { + return error.GetifaddrsFailed; + } + defer c_ifaddrs.freeifaddrs(ifap); + + var cur: ?*c_ifaddrs.ifaddrs = ifap; + while (cur) |ifa| : (cur = ifa.ifa_next) { + if (ifa.ifa_addr) |addr| { + // Skips the loopback + if ((ifa.ifa_flags & c_net_if.IFF_LOOPBACK) != 0) continue; + + const sockaddr_ptr = @as(*const c_socket.sockaddr, @ptrCast(@alignCast(addr))); + + if (sockaddr_ptr.sa_family != c_inet.AF_INET) continue; + + var addr_in = @as(*const c_netinet_in.sockaddr_in, @ptrCast(@alignCast(sockaddr_ptr))); + var ip_buf: [c_inet.INET_ADDRSTRLEN]u8 = undefined; + const ip_str = c_inet.inet_ntop(c_inet.AF_INET, &addr_in.sin_addr, &ip_buf, c_inet.INET_ADDRSTRLEN); + if (ip_str) |ip| { + try net_info_list.append(NetInfo{ + .interface_name = try allocator.dupe(u8, std.mem.span(ifa.ifa_name)), + .ipv4_addr = try allocator.dupe(u8, std.mem.span(ip)), + }); + } + } + } + + return net_info_list; +} diff --git a/src/linux/system.zig b/src/linux/system.zig new file mode 100644 index 0000000..42756f4 --- /dev/null +++ b/src/linux/system.zig @@ -0,0 +1,92 @@ +const std = @import("std"); +const c_sysinfo = @cImport(@cInclude("sys/sysinfo.h")); +const c_utsname = @cImport(@cInclude("sys/utsname.h")); + +/// Structure representing system uptime in days, hours, and minutes. +pub const SystemUptime = struct { + days: i8, + hours: i8, + minutes: i8, +}; + +pub const KernelInfo = struct { + kernel_name: []u8, + kernel_release: []u8, +}; + +pub fn getHostname(allocator: std.mem.Allocator) ![]u8 { + var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; + const hostnameEnv = try std.posix.gethostname(&buf); + + const hostname = try allocator.dupe(u8, hostnameEnv); + + return hostname; +} + +/// Returns the system uptime. +/// +/// Uses `sysinfo` to fetch the system uptime and calculates the elapsed time. +pub fn getSystemUptime() !SystemUptime { + const seconds_per_day: f64 = 86400.0; + const hours_per_day: f64 = 24.0; + const seconds_per_hour: f64 = 3600.0; + const seconds_per_minute: f64 = 60.0; + + var info: c_sysinfo.struct_sysinfo = undefined; + if (c_sysinfo.sysinfo(&info) != 0) { + return error.SysinfoFailed; + } + + const uptime_seconds: f64 = @as(f64, @floatFromInt(info.uptime)); + + var remainig_seconds: f64 = uptime_seconds; + const days: f64 = @floor(remainig_seconds / seconds_per_day); + + remainig_seconds = (remainig_seconds / seconds_per_day) - days; + const hours = @floor(remainig_seconds * hours_per_day); + + remainig_seconds = (remainig_seconds * hours_per_day) - hours; + const minutes = @floor((remainig_seconds * seconds_per_hour) / seconds_per_minute); + + return SystemUptime{ + .days = @as(i8, @intFromFloat(days)), + .hours = @as(i8, @intFromFloat(hours)), + .minutes = @as(i8, @intFromFloat(minutes)), + }; +} + +pub fn getKernelInfo(allocator: std.mem.Allocator) !KernelInfo { + var uts: c_utsname.struct_utsname = undefined; + if (c_utsname.uname(&uts) != 0) { + return error.UnameFailed; + } + + return KernelInfo{ + .kernel_name = try allocator.dupe(u8, &uts.sysname), + .kernel_release = try allocator.dupe(u8, &uts.release), + }; +} + +pub fn getOsInfo(allocator: std.mem.Allocator) ![]u8 { + const os_release_path = "/etc/os-release"; + const file = try std.fs.cwd().openFile(os_release_path, .{}); + defer file.close(); + const os_release_data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(os_release_data); + + var pretty_name: ?[]const u8 = null; + + var lines = std.mem.split(u8, os_release_data, "\n"); + while (lines.next()) |line| { + if (std.mem.startsWith(u8, line, "PRETTY_NAME")) { + var parts = std.mem.split(u8, line, "="); + _ = parts.next(); // discard the key + if (parts.next()) |value| { + pretty_name = std.mem.trim(u8, value, "\""); + break; + } + } + } + + return try allocator.dupe(u8, pretty_name orelse "Unknown"); +} diff --git a/src/linux/user.zig b/src/linux/user.zig new file mode 100644 index 0000000..acff28f --- /dev/null +++ b/src/linux/user.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +pub fn getUsername(allocator: std.mem.Allocator) ![]u8 { + const username = try std.process.getEnvVarOwned(allocator, "USER"); + return username; +} + +pub fn getShell(allocator: std.mem.Allocator) ![]u8 { + const shell = try std.process.getEnvVarOwned(allocator, "SHELL"); + + var child = std.process.Child.init(&[_][]const u8{ shell, "--version" }, allocator); + + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const output = try child.stdout.?.reader().readAllAlloc(allocator, 1024 * 1024); + + _ = try child.wait(); + + return output; +} + +pub fn getTerminalName(allocator: std.mem.Allocator) ![]u8 { + const term_progrm = try std.process.getEnvVarOwned(allocator, "TERM_PROGRAM"); + return term_progrm; +} diff --git a/src/macos/hardware.zig b/src/macos/hardware.zig new file mode 100644 index 0000000..375b0c0 --- /dev/null +++ b/src/macos/hardware.zig @@ -0,0 +1,302 @@ +const std = @import("std"); +const utils = @import("utils.zig"); +const c_sysctl = @cImport(@cInclude("sys/sysctl.h")); +const c_iokit = @cImport(@cInclude("IOKit/IOKitLib.h")); +const c_cf = @cImport(@cInclude("CoreFoundation/CoreFoundation.h")); +const c_mach = @cImport(@cInclude("mach/mach.h")); +const c_statvfs = @cImport(@cInclude("sys/statvfs.h")); + +pub const CpuInfo = struct { + cpu_name: []u8, + cpu_cores: i32, + cpu_max_freq: f64, +}; + +pub const GpuInfo = struct { + gpu_name: []u8, + gpu_cores: i32, +}; + +pub const RamInfo = struct { + ram_size: f64, + ram_usage: f64, + ram_usage_percentage: u8, +}; + +pub const SwapInfo = struct { + swap_size: f64, + swap_usage: f64, + swap_usage_percentage: u64, +}; + +pub const DiskInfo = struct { + disk_path: []const u8, + disk_size: f64, + disk_usage: f64, + disk_usage_percentage: u8, +}; + +pub fn getCpuInfo(allocator: std.mem.Allocator) !CpuInfo { + var size: usize = 0; + + // First call to sysctlbyname to get the size of the string + if (c_sysctl.sysctlbyname("machdep.cpu.brand_string", null, &size, null, 0) != 0) { + return error.FailedToGetCpuNameSize; + } + + const cpu_name: []u8 = try allocator.alloc(u8, size - 1); + errdefer allocator.free(cpu_name); + + // Second call to sysctlbyname to get the CPU name + if (c_sysctl.sysctlbyname("machdep.cpu.brand_string", cpu_name.ptr, &size, null, 0) != 0) { + return error.FailedToGetCpuName; + } + + // Call to sysctlbyname to get the cpu cores + var n_cpu: i32 = 0; + size = @sizeOf(i32); + if (c_sysctl.sysctlbyname("hw.ncpu", &n_cpu, &size, null, 0) != 0) { + return error.FailedToGetPhysicalCpuInfo; + } + + // TODO: add cpu frequency for Intel + + const cpu_freq_mhz = try getCpuFreqAppleSilicon(); + + const cpu_freq_ghz = @floor(cpu_freq_mhz) / 1000; + + return CpuInfo{ .cpu_name = cpu_name, .cpu_cores = n_cpu, .cpu_max_freq = cpu_freq_ghz }; +} + +fn getCpuFreqAppleSilicon() !f64 { + // https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/cpu/cpu_apple.c + + // Retrieve the matching service for "pmgr" + // https://developer.apple.com/documentation/iokit/1514535-ioservicegetmatchingservice + const service = c_iokit.IOServiceGetMatchingService(c_iokit.kIOMasterPortDefault, c_iokit.IOServiceNameMatching("pmgr")); + if (service == c_iokit.FALSE) return error.NoMatchingService; + defer _ = c_iokit.IOObjectRelease(service); + + // Check that the service conforms to "AppleARMIODevice" + // https://developer.apple.com/documentation/iokit/1514505-ioobjectconformsto + if (c_iokit.IOObjectConformsTo(service, "AppleARMIODevice") == c_iokit.FALSE) { + return error.NotAppleARMIODevice; + } + + // CFSTR is a macro and can't be translated by Zig + // The CFString is created "manually" + const vs5s_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "voltage-states5-sram", c_iokit.kCFStringEncodingUTF8); + if (vs5s_key == null) { + return error.FailedToCreateCFKey; + } + defer c_iokit.CFRelease(vs5s_key); + + // Retrieve the property from the registry entry + // https://developer.apple.com/documentation/iokit/1514293-ioregistryentrycreatecfproperty + const freq_property = c_iokit.IORegistryEntryCreateCFProperty(service, vs5s_key, c_iokit.kCFAllocatorDefault, 0); + if (freq_property == null) return error.PropertyNotFound; + defer c_iokit.CFRelease(freq_property); + + // Ensure the property is a CFData object + if (c_iokit.CFGetTypeID(freq_property) != c_cf.CFDataGetTypeID()) + return error.InvalidPropertyType; + + const freq_data = @as(*const c_iokit.__CFData, @ptrCast(freq_property)); + + // Get the length of the CFData + const freq_data_length = c_iokit.CFDataGetLength(freq_data); + + // voltage-states5-sram stores supported pairs of pcores from the lowest to the highest + if (freq_data_length == 0 or @as(u32, @intCast(freq_data_length)) % (@sizeOf(u32) * 2) != 0) + return error.InvalidVoltageStates5SramLength; + + // Get data pointer + const freq_data_ptr = c_iokit.CFDataGetBytePtr(freq_data); + if (freq_data_ptr == null) + return error.InvalidVoltageStates5SramData; + + const freq_array = @as([*]const u32, @ptrCast(@alignCast(freq_data_ptr))); + + // The first element contains the minimum freq + var p_max: u32 = freq_array[0]; + + const total_elements = @as(u32, @intCast(freq_data_length)) / @sizeOf(u32); + + // Iterate on values, starting at index 2, skipping voltage (each pair is ) + var i: usize = 2; + while (i < total_elements) : (i += 2) { + const current = freq_array[i]; + if (current == 0) break; + if (current > p_max) { + p_max = current; + } + } + + // Assume that p_max is in Hz, M1~M3 + if (p_max > 100_000_000) { + return @as(f64, @floatFromInt(p_max)) / 1_000 / 1_000; + } else { // Assume that p_max is in kHz, M4 and later + return @as(f64, @floatFromInt(p_max)) / 1_000; + } +} + +pub fn getGpuInfo(allocator: std.mem.Allocator) !GpuInfo { + // TODO: add support for non-Apple Silicon Macs + + var gpu_info = GpuInfo{ + .gpu_name = try allocator.dupe(u8, "Unknown"), + .gpu_cores = 0, + }; + + // https://developer.apple.com/documentation/iokit/1514687-ioservicematching + const accel_matching_dict = c_iokit.IOServiceMatching("IOAccelerator"); + if (accel_matching_dict == null) { + return error.MatchingDictionaryCreationFailed; + } + + var iterator: c_iokit.io_iterator_t = undefined; + // https://developer.apple.com/documentation/iokit/1514494-ioservicegetmatchingservices + const result = c_iokit.IOServiceGetMatchingServices(c_iokit.kIOMasterPortDefault, accel_matching_dict, &iterator); + + if (result != c_iokit.KERN_SUCCESS) { + return error.ServiceMatchingFailed; + } + defer _ = c_iokit.IOObjectRelease(iterator); + + const service = c_iokit.IOIteratorNext(iterator); + if (service != 0) { + defer _ = c_iokit.IOObjectRelease(service); + + var properties_ptr: c_iokit.CFMutableDictionaryRef = null; + const properties_ptr_ref: [*c]c_iokit.CFMutableDictionaryRef = &properties_ptr; + + // https://developer.apple.com/documentation/iokit/1514310-ioregistryentrycreatecfpropertie + if (c_iokit.IORegistryEntryCreateCFProperties(service, properties_ptr_ref, c_iokit.kCFAllocatorDefault, 0) != c_iokit.KERN_SUCCESS) { + return gpu_info; + } + + if (properties_ptr == null) { + return gpu_info; + } + defer c_iokit.CFRelease(properties_ptr); + + var name_ref: c_iokit.CFTypeRef = undefined; + var cores_ref: c_iokit.CFTypeRef = undefined; + + // CFSTR is a macro and can't be translated by Zig + // The CFString is created "manually" + const model_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "model", c_iokit.kCFStringEncodingUTF8); + if (model_key == null) return gpu_info; + defer c_iokit.CFRelease(model_key); + + if (c_iokit.CFDictionaryGetValueIfPresent(@as(c_iokit.CFDictionaryRef, @ptrCast(properties_ptr)), model_key, &name_ref) == c_iokit.TRUE) { + if (c_iokit.CFGetTypeID(name_ref) == c_iokit.CFStringGetTypeID()) { + const accel_name = utils.cfStringToZigString(allocator, @as(c_iokit.CFStringRef, @ptrCast(name_ref))) catch { + return gpu_info; + }; + + allocator.free(gpu_info.gpu_name); + gpu_info.gpu_name = accel_name; + } + } + + // CFSTR is a macro and can't be translated by Zig + // The CFString is created "manually" + const gpu_core_count_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "gpu-core-count", c_iokit.kCFStringEncodingUTF8); + if (gpu_core_count_key == null) return gpu_info; + defer c_iokit.CFRelease(gpu_core_count_key); + + if (c_iokit.CFDictionaryGetValueIfPresent(@as(c_iokit.CFDictionaryRef, @ptrCast(properties_ptr)), gpu_core_count_key, &cores_ref) == c_iokit.TRUE) { + if (c_iokit.CFGetTypeID(cores_ref) == c_cf.CFNumberGetTypeID()) { + var cores_num: i32 = 0; + if (c_cf.CFNumberGetValue(@as(c_cf.CFNumberRef, @ptrCast(cores_ref)), c_cf.kCFNumberIntType, &cores_num) == c_cf.TRUE) { + gpu_info.gpu_cores = cores_num; + } + } + } + } + + return gpu_info; +} + +pub fn getRamInfo() !RamInfo { + // -- RAM SIZE -- + var ram_size: u64 = 0; + var ram_size_len: usize = @sizeOf(u64); + var name = [_]c_int{ c_sysctl.CTL_HW, c_sysctl.HW_MEMSIZE }; + if (c_sysctl.sysctl(&name, name.len, &ram_size, &ram_size_len, null, 0) != 0) { + return error.FailedToGetRamSize; + } + + // Converts Bytes to Gigabytes + const ram_size_gb: f64 = @as(f64, @floatFromInt(ram_size)) / (1024 * 1024 * 1024); + + // -- RAM USAGE -- + var info: c_mach.vm_statistics64 = undefined; + var count: c_mach.mach_msg_type_number_t = @sizeOf(c_mach.vm_statistics64) / @sizeOf(c_mach.integer_t); + const host_port = c_mach.mach_host_self(); + + if (c_mach.host_statistics64(host_port, c_mach.HOST_VM_INFO64, @ptrCast(&info), &count) != c_mach.KERN_SUCCESS) { + return error.HostStatistics64Failed; + } + + const page_size: u64 = std.heap.page_size_min; + + // https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/memory/memory_apple.c + const ram_usage = (info.active_count + info.inactive_count + info.speculative_count + info.wire_count + info.compressor_page_count - info.purgeable_count - info.external_page_count) * page_size; + + // Converts Bytes to Gigabytes + const ram_usage_gb: f64 = @as(f64, @floatFromInt(ram_usage)) / (1024 * 1024 * 1024); + + const ram_usage_percentage: u8 = @as(u8, @intFromFloat((ram_usage_gb * 100) / ram_size_gb)); + + return RamInfo{ + .ram_size = ram_size_gb, + .ram_usage = ram_usage_gb, + .ram_usage_percentage = ram_usage_percentage, + }; +} + +pub fn getSwapInfo() !?SwapInfo { + var swap: c_sysctl.struct_xsw_usage = undefined; + var size: usize = @sizeOf(c_sysctl.struct_xsw_usage); + + if (c_sysctl.sysctlbyname("vm.swapusage", &swap, &size, null, 0) != 0) { + return error.FailedToGetSwapInfo; + } + + const swap_size = @as(f64, @floatFromInt(swap.xsu_total / (1024 * 1024 * 1024))); + const swap_usage = @as(f64, @floatFromInt(swap.xsu_used / (1024 * 1024 * 1024))); + var swap_usage_percentage: u64 = 0; + if (@as(u64, swap.xsu_total) != 0) { + swap_usage_percentage = (@as(u64, swap.xsu_used) * 100) / @as(u64, swap.xsu_total); + } else { + return null; + } + + return SwapInfo{ + .swap_size = swap_size, + .swap_usage = swap_usage, + .swap_usage_percentage = swap_usage_percentage, + }; +} + +pub fn getDiskSize(disk_path: []const u8) !DiskInfo { + var stat: c_statvfs.struct_statvfs = undefined; + if (c_statvfs.statvfs(disk_path.ptr, &stat) != 0) { + return error.StatvfsFailed; + } + + const total_size = stat.f_blocks * stat.f_frsize; + const free_size = stat.f_bavail * stat.f_frsize; + const used_size = total_size - free_size; + + const used_size_percentage = (used_size * 100) / total_size; + + return DiskInfo{ + .disk_path = disk_path, + .disk_size = @as(f64, @floatFromInt(total_size)) / 1e9, + .disk_usage = @as(f64, @floatFromInt(used_size)) / 1e9, + .disk_usage_percentage = @as(u8, @intCast(used_size_percentage)), + }; +} diff --git a/src/macos/macos.zig b/src/macos/macos.zig index c723fda..f7ee652 100644 --- a/src/macos/macos.zig +++ b/src/macos/macos.zig @@ -1,502 +1,4 @@ -const std = @import("std"); -const utils = @import("./utils.zig"); -const c_libproc = @cImport(@cInclude("libproc.h")); -const c_sysctl = @cImport(@cInclude("sys/sysctl.h")); -const c_iokit = @cImport(@cInclude("IOKit/IOKitLib.h")); -const c_cf = @cImport(@cInclude("CoreFoundation/CoreFoundation.h")); -const c_mach = @cImport(@cInclude("mach/mach.h")); -const c_statvfs = @cImport(@cInclude("sys/statvfs.h")); -const c_ifaddrs = @cImport(@cInclude("ifaddrs.h")); -const c_inet = @cImport(@cInclude("arpa/inet.h")); -const c_net_if = @cImport(@cInclude("net/if.h")); -const c_netinet_in = @cImport(@cInclude("netinet/in.h")); -const c_socket = @cImport(@cInclude("sys/socket.h")); - -/// Structure representing system uptime in days, hours, and minutes. -pub const SystemUptime = struct { - days: i8, - hours: i8, - minutes: i8, -}; - -pub const CpuInfo = struct { - cpu_name: []u8, - cpu_cores: i32, - cpu_max_freq: f64, -}; - -pub const GpuInfo = struct { - gpu_name: []u8, - gpu_cores: i32, -}; - -pub const KernelInfo = struct { - kernel_name: []u8, - kernel_release: []u8, -}; - -pub const RamInfo = struct { - ram_size: f64, - ram_usage: f64, - ram_usage_percentage: u8, -}; - -pub const DiskInfo = struct { - disk_path: []const u8, - disk_size: f64, - disk_usage: f64, - disk_usage_percentage: u8, -}; - -pub const SwapInfo = struct { - swap_size: f64, - swap_usage: f64, - swap_usage_percentage: u64, -}; - -pub const NetInfo = struct { - interface_name: []u8, - ipv4_addr: []u8, -}; - -/// Returns the current logged-in uesr's username. -/// Uses the environment variable `USER`. -/// The caller is responsible for freeing the allocated memory. -pub fn getUsername(allocator: std.mem.Allocator) ![]u8 { - const username = try std.process.getEnvVarOwned(allocator, "USER"); - return username; -} - -/// Returns the hostname. -pub fn getHostname(allocator: std.mem.Allocator) ![]u8 { - var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; - const hostnameEnv = try std.posix.gethostname(&buf); - - const hostname = try allocator.dupe(u8, hostnameEnv); - - return hostname; -} - -pub fn getLocale(allocator: std.mem.Allocator) ![]u8 { - const locale = try std.process.getEnvVarOwned(allocator, "LANG"); - return locale; -} - -/// Returns the system uptime. -/// -/// Uses `sysctl` to fetch the system boot time and calculates the elapsed time. -pub fn getSystemUptime() !SystemUptime { - const seconds_per_day: f64 = 86400.0; - const hours_per_day: f64 = 24.0; - const seconds_per_hour: f64 = 3600.0; - const seconds_per_minute: f64 = 60.0; - - var boot_time: c_libproc.struct_timeval = undefined; - var size: usize = @sizeOf(c_libproc.struct_timeval); - - var uptime_seconds: f64 = 0.0; - - var name = [_]c_int{ c_sysctl.CTL_KERN, c_sysctl.KERN_BOOTTIME }; - if (c_sysctl.sysctl(&name, name.len, &boot_time, &size, null, 0) == 0) { - const boot_seconds = @as(f64, @floatFromInt(boot_time.tv_sec)); - const now_seconds = @as(f64, @floatFromInt(std.time.timestamp())); - uptime_seconds = now_seconds - boot_seconds; - } else { - return error.UnableToGetSystemUptime; - } - - var remainig_seconds: f64 = uptime_seconds; - const days: f64 = @floor(remainig_seconds / seconds_per_day); - - remainig_seconds = (remainig_seconds / seconds_per_day) - days; - const hours = @floor(remainig_seconds * hours_per_day); - - remainig_seconds = (remainig_seconds * hours_per_day) - hours; - const minutes = @floor((remainig_seconds * seconds_per_hour) / seconds_per_minute); - - return SystemUptime{ - .days = @as(i8, @intFromFloat(days)), - .hours = @as(i8, @intFromFloat(hours)), - .minutes = @as(i8, @intFromFloat(minutes)), - }; -} - -pub fn getShell(allocator: std.mem.Allocator) ![]u8 { - const shell = try std.process.getEnvVarOwned(allocator, "SHELL"); - - var child = std.process.Child.init(&[_][]const u8{ shell, "--version" }, allocator); - - child.stdout_behavior = .Pipe; - child.stderr_behavior = .Pipe; - - try child.spawn(); - - const output = try child.stdout.?.reader().readAllAlloc(allocator, 1024 * 1024); - - _ = try child.wait(); - - return output; -} - -pub fn getCpuInfo(allocator: std.mem.Allocator) !CpuInfo { - var size: usize = 0; - - // First call to sysctlbyname to get the size of the string - if (c_sysctl.sysctlbyname("machdep.cpu.brand_string", null, &size, null, 0) != 0) { - return error.FailedToGetCpuNameSize; - } - - const cpu_name: []u8 = try allocator.alloc(u8, size - 1); - errdefer allocator.free(cpu_name); - - // Second call to sysctlbyname to get the CPU name - if (c_sysctl.sysctlbyname("machdep.cpu.brand_string", cpu_name.ptr, &size, null, 0) != 0) { - return error.FailedToGetCpuName; - } - - // Call to sysctlbyname to get the cpu cores - var n_cpu: i32 = 0; - size = @sizeOf(i32); - if (c_sysctl.sysctlbyname("hw.ncpu", &n_cpu, &size, null, 0) != 0) { - return error.FailedToGetPhysicalCpuInfo; - } - - // TODO: add cpu frequency for Intel - - const cpu_freq_mhz = try getCpuFreqAppleSilicon(); - - const cpu_freq_ghz = @floor(cpu_freq_mhz) / 1000; - - return CpuInfo{ .cpu_name = cpu_name, .cpu_cores = n_cpu, .cpu_max_freq = cpu_freq_ghz }; -} - -fn getCpuFreqAppleSilicon() !f64 { - // https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/cpu/cpu_apple.c - - // Retrieve the matching service for "pmgr" - // https://developer.apple.com/documentation/iokit/1514535-ioservicegetmatchingservice - const service = c_iokit.IOServiceGetMatchingService(c_iokit.kIOMasterPortDefault, c_iokit.IOServiceNameMatching("pmgr")); - if (service == c_iokit.FALSE) return error.NoMatchingService; - defer _ = c_iokit.IOObjectRelease(service); - - // Check that the service conforms to "AppleARMIODevice" - // https://developer.apple.com/documentation/iokit/1514505-ioobjectconformsto - if (c_iokit.IOObjectConformsTo(service, "AppleARMIODevice") == c_iokit.FALSE) { - return error.NotAppleARMIODevice; - } - - // CFSTR is a macro and can't be translated by Zig - // The CFString is created "manually" - const vs5s_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "voltage-states5-sram", c_iokit.kCFStringEncodingUTF8); - if (vs5s_key == null) { - return error.FailedToCreateCFKey; - } - defer c_iokit.CFRelease(vs5s_key); - - // Retrieve the property from the registry entry - // https://developer.apple.com/documentation/iokit/1514293-ioregistryentrycreatecfproperty - const freq_property = c_iokit.IORegistryEntryCreateCFProperty(service, vs5s_key, c_iokit.kCFAllocatorDefault, 0); - if (freq_property == null) return error.PropertyNotFound; - defer c_iokit.CFRelease(freq_property); - - // Ensure the property is a CFData object - if (c_iokit.CFGetTypeID(freq_property) != c_cf.CFDataGetTypeID()) - return error.InvalidPropertyType; - - const freq_data = @as(*const c_iokit.__CFData, @ptrCast(freq_property)); - - // Get the length of the CFData - const freq_data_length = c_iokit.CFDataGetLength(freq_data); - - // voltage-states5-sram stores supported pairs of pcores from the lowest to the highest - if (freq_data_length == 0 or @as(u32, @intCast(freq_data_length)) % (@sizeOf(u32) * 2) != 0) - return error.InvalidVoltageStates5SramLength; - - // Get data pointer - const freq_data_ptr = c_iokit.CFDataGetBytePtr(freq_data); - if (freq_data_ptr == null) - return error.InvalidVoltageStates5SramData; - - const freq_array = @as([*]const u32, @ptrCast(@alignCast(freq_data_ptr))); - - // The first element contains the minimum freq - var p_max: u32 = freq_array[0]; - - const total_elements = @as(u32, @intCast(freq_data_length)) / @sizeOf(u32); - - // Iterate on values, starting at index 2, skipping voltage (each pair is ) - var i: usize = 2; - while (i < total_elements) : (i += 2) { - const current = freq_array[i]; - if (current == 0) break; - if (current > p_max) { - p_max = current; - } - } - - // Assume that p_max is in Hz, M1~M3 - if (p_max > 100_000_000) { - return @as(f64, @floatFromInt(p_max)) / 1_000 / 1_000; - } else { // Assume that p_max is in kHz, M4 and later - return @as(f64, @floatFromInt(p_max)) / 1_000; - } -} - -/// Returns the gpu info. -pub fn getGpuInfo(allocator: std.mem.Allocator) !GpuInfo { - // TODO: add support for non-Apple Silicon Macs - - var gpu_info = GpuInfo{ - .gpu_name = try allocator.dupe(u8, "Unknown"), - .gpu_cores = 0, - }; - - // https://developer.apple.com/documentation/iokit/1514687-ioservicematching - const accel_matching_dict = c_iokit.IOServiceMatching("IOAccelerator"); - if (accel_matching_dict == null) { - return error.MatchingDictionaryCreationFailed; - } - - var iterator: c_iokit.io_iterator_t = undefined; - // https://developer.apple.com/documentation/iokit/1514494-ioservicegetmatchingservices - const result = c_iokit.IOServiceGetMatchingServices(c_iokit.kIOMasterPortDefault, accel_matching_dict, &iterator); - - if (result != c_iokit.KERN_SUCCESS) { - return error.ServiceMatchingFailed; - } - defer _ = c_iokit.IOObjectRelease(iterator); - - const service = c_iokit.IOIteratorNext(iterator); - if (service != 0) { - defer _ = c_iokit.IOObjectRelease(service); - - var properties_ptr: c_iokit.CFMutableDictionaryRef = null; - const properties_ptr_ref: [*c]c_iokit.CFMutableDictionaryRef = &properties_ptr; - - // https://developer.apple.com/documentation/iokit/1514310-ioregistryentrycreatecfpropertie - if (c_iokit.IORegistryEntryCreateCFProperties(service, properties_ptr_ref, c_iokit.kCFAllocatorDefault, 0) != c_iokit.KERN_SUCCESS) { - return gpu_info; - } - - if (properties_ptr == null) { - return gpu_info; - } - defer c_iokit.CFRelease(properties_ptr); - - var name_ref: c_iokit.CFTypeRef = undefined; - var cores_ref: c_iokit.CFTypeRef = undefined; - - // CFSTR is a macro and can't be translated by Zig - // The CFString is created "manually" - const model_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "model", c_iokit.kCFStringEncodingUTF8); - if (model_key == null) return gpu_info; - defer c_iokit.CFRelease(model_key); - - if (c_iokit.CFDictionaryGetValueIfPresent(@as(c_iokit.CFDictionaryRef, @ptrCast(properties_ptr)), model_key, &name_ref) == c_iokit.TRUE) { - if (c_iokit.CFGetTypeID(name_ref) == c_iokit.CFStringGetTypeID()) { - const accel_name = utils.cfStringToZigString(allocator, @as(c_iokit.CFStringRef, @ptrCast(name_ref))) catch { - return gpu_info; - }; - - allocator.free(gpu_info.gpu_name); - gpu_info.gpu_name = accel_name; - } - } - - // CFSTR is a macro and can't be translated by Zig - // The CFString is created "manually" - const gpu_core_count_key = c_iokit.CFStringCreateWithCString(c_iokit.kCFAllocatorDefault, "gpu-core-count", c_iokit.kCFStringEncodingUTF8); - if (gpu_core_count_key == null) return gpu_info; - defer c_iokit.CFRelease(gpu_core_count_key); - - if (c_iokit.CFDictionaryGetValueIfPresent(@as(c_iokit.CFDictionaryRef, @ptrCast(properties_ptr)), gpu_core_count_key, &cores_ref) == c_iokit.TRUE) { - if (c_iokit.CFGetTypeID(cores_ref) == c_cf.CFNumberGetTypeID()) { - var cores_num: i32 = 0; - if (c_cf.CFNumberGetValue(@as(c_cf.CFNumberRef, @ptrCast(cores_ref)), c_cf.kCFNumberIntType, &cores_num) == c_cf.TRUE) { - gpu_info.gpu_cores = cores_num; - } - } - } - } - - return gpu_info; -} - -pub fn getRamInfo() !RamInfo { - // -- RAM SIZE -- - var ram_size: u64 = 0; - var ram_size_len: usize = @sizeOf(u64); - var name = [_]c_int{ c_sysctl.CTL_HW, c_sysctl.HW_MEMSIZE }; - if (c_sysctl.sysctl(&name, name.len, &ram_size, &ram_size_len, null, 0) != 0) { - return error.FailedToGetRamSize; - } - - // Converts Bytes to Gigabytes - const ram_size_gb: f64 = @as(f64, @floatFromInt(ram_size)) / (1024 * 1024 * 1024); - - // -- RAM USAGE -- - var info: c_mach.vm_statistics64 = undefined; - var count: c_mach.mach_msg_type_number_t = @sizeOf(c_mach.vm_statistics64) / @sizeOf(c_mach.integer_t); - const host_port = c_mach.mach_host_self(); - - if (c_mach.host_statistics64(host_port, c_mach.HOST_VM_INFO64, @ptrCast(&info), &count) != c_mach.KERN_SUCCESS) { - return error.HostStatistics64Failed; - } - - const page_size: u64 = std.heap.page_size_min; - - // https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/memory/memory_apple.c - const ram_usage = (info.active_count + info.inactive_count + info.speculative_count + info.wire_count + info.compressor_page_count - info.purgeable_count - info.external_page_count) * page_size; - - // Converts Bytes to Gigabytes - const ram_usage_gb: f64 = @as(f64, @floatFromInt(ram_usage)) / (1024 * 1024 * 1024); - - const ram_usage_percentage: u8 = @as(u8, @intFromFloat((ram_usage_gb * 100) / ram_size_gb)); - - return RamInfo{ - .ram_size = ram_size_gb, - .ram_usage = ram_usage_gb, - .ram_usage_percentage = ram_usage_percentage, - }; -} - -pub fn getDiskSize(disk_path: []const u8) !DiskInfo { - var stat: c_statvfs.struct_statvfs = undefined; - if (c_statvfs.statvfs(disk_path.ptr, &stat) != 0) { - return error.StatvfsFailed; - } - - const total_size = stat.f_blocks * stat.f_frsize; - const free_size = stat.f_bavail * stat.f_frsize; - const used_size = total_size - free_size; - - const used_size_percentage = (used_size * 100) / total_size; - - return DiskInfo{ - .disk_path = disk_path, - .disk_size = @as(f64, @floatFromInt(total_size)) / 1e9, - .disk_usage = @as(f64, @floatFromInt(used_size)) / 1e9, - .disk_usage_percentage = @as(u8, @intCast(used_size_percentage)), - }; -} - -pub fn getTerminalName(allocator: std.mem.Allocator) ![]u8 { - const term_progrm = try std.process.getEnvVarOwned(allocator, "TERM_PROGRAM"); - return term_progrm; -} - -pub fn getKernelInfo(allocator: std.mem.Allocator) !KernelInfo { - var size: usize = 0; - - // --- KERNEL NAME --- - // First call to sysctlbyname to get the size of the string - if (c_sysctl.sysctlbyname("kern.ostype", null, &size, null, 0) != 0) { - return error.FailedToGetKernelNameSize; - } - - const kernel_type: []u8 = try allocator.alloc(u8, size - 1); - errdefer allocator.free(kernel_type); - - // Second call to sysctlbyname to get the kernel name - if (c_sysctl.sysctlbyname("kern.ostype", kernel_type.ptr, &size, null, 0) != 0) { - return error.FailedToGetKernelName; - } - - // --- KERNEL RELEASE --- - // First call to sysctlbyname to get the size of the string - if (c_sysctl.sysctlbyname("kern.osrelease", null, &size, null, 0) != 0) { - return error.FailedToGetKernelReleaseSize; - } - - const os_release: []u8 = try allocator.alloc(u8, size - 1); - errdefer allocator.free(os_release); - - // Second call to sysctlbyname to get the kernel release - if (c_sysctl.sysctlbyname("kern.osrelease", os_release.ptr, &size, null, 0) != 0) { - return error.FailedToGetKernelRelease; - } - - return KernelInfo{ - .kernel_name = kernel_type, - .kernel_release = os_release, - }; -} - -pub fn getOsInfo(allocator: std.mem.Allocator) ![]u8 { - var size: usize = 0; - - // First call to sysctlbyname to get the size of the string - if (c_sysctl.sysctlbyname("kern.osproductversion", null, &size, null, 0) != 0) { - return error.FailedToGetCpuNameSize; - } - - const os_version: []u8 = try allocator.alloc(u8, size - 1); - defer allocator.free(os_version); - - // Second call to sysctlbyname to get the os version - if (c_sysctl.sysctlbyname("kern.osproductversion", os_version.ptr, &size, null, 0) != 0) { - return error.FailedToGetOsVersion; - } - - const os_info = try std.fmt.allocPrint(allocator, "macOS {s}", .{os_version}); - - return os_info; -} - -pub fn getSwapInfo() !?SwapInfo { - var swap: c_sysctl.struct_xsw_usage = undefined; - var size: usize = @sizeOf(c_sysctl.struct_xsw_usage); - - if (c_sysctl.sysctlbyname("vm.swapusage", &swap, &size, null, 0) != 0) { - return error.FailedToGetSwapInfo; - } - - const swap_size = @as(f64, @floatFromInt(swap.xsu_total / (1024 * 1024 * 1024))); - const swap_usage = @as(f64, @floatFromInt(swap.xsu_used / (1024 * 1024 * 1024))); - var swap_usage_percentage: u64 = 0; - if (@as(u64, swap.xsu_total) != 0) { - swap_usage_percentage = (@as(u64, swap.xsu_used) * 100) / @as(u64, swap.xsu_total); - } else { - return null; - } - - return SwapInfo{ - .swap_size = swap_size, - .swap_usage = swap_usage, - .swap_usage_percentage = swap_usage_percentage, - }; -} - -pub fn getNetInfo(allocator: std.mem.Allocator) !std.ArrayList(NetInfo) { - var net_info_list = std.ArrayList(NetInfo).init(allocator); - - var ifap: ?*c_ifaddrs.ifaddrs = null; - if (c_ifaddrs.getifaddrs(&ifap) != 0) { - return error.GetifaddrsFailed; - } - defer c_ifaddrs.freeifaddrs(ifap); - - var cur: ?*c_ifaddrs.ifaddrs = ifap; - while (cur) |ifa| : (cur = ifa.ifa_next) { - if (ifa.ifa_addr) |addr| { - // Skips the loopback - if ((ifa.ifa_flags & c_net_if.IFF_LOOPBACK) != 0) continue; - - const sockaddr_ptr = @as(*const c_socket.sockaddr, @ptrCast(@alignCast(addr))); - - if (sockaddr_ptr.sa_family != c_inet.AF_INET) continue; - - var addr_in = @as(*const c_netinet_in.sockaddr_in, @ptrCast(@alignCast(sockaddr_ptr))); - var ip_buf: [c_inet.INET_ADDRSTRLEN]u8 = undefined; - const ip_str = c_inet.inet_ntop(c_inet.AF_INET, &addr_in.sin_addr, &ip_buf, c_inet.INET_ADDRSTRLEN); - if (ip_str) |ip| { - try net_info_list.append(NetInfo{ - .interface_name = try allocator.dupe(u8, std.mem.span(ifa.ifa_name)), - .ipv4_addr = try allocator.dupe(u8, std.mem.span(ip)), - }); - } - } - } - - return net_info_list; -} +pub const hardware = @import("./hardware.zig"); +pub const network = @import("./network.zig"); +pub const system = @import("./system.zig"); +pub const user = @import("./user.zig"); diff --git a/src/macos/network.zig b/src/macos/network.zig new file mode 100644 index 0000000..b8da949 --- /dev/null +++ b/src/macos/network.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const c_ifaddrs = @cImport(@cInclude("ifaddrs.h")); +const c_inet = @cImport(@cInclude("arpa/inet.h")); +const c_net_if = @cImport(@cInclude("net/if.h")); +const c_netinet_in = @cImport(@cInclude("netinet/in.h")); +const c_socket = @cImport(@cInclude("sys/socket.h")); + +pub const NetInfo = struct { + interface_name: []u8, + ipv4_addr: []u8, +}; + +pub fn getNetInfo(allocator: std.mem.Allocator) !std.ArrayList(NetInfo) { + var net_info_list = std.ArrayList(NetInfo).init(allocator); + + var ifap: ?*c_ifaddrs.ifaddrs = null; + if (c_ifaddrs.getifaddrs(&ifap) != 0) { + return error.GetifaddrsFailed; + } + defer c_ifaddrs.freeifaddrs(ifap); + + var cur: ?*c_ifaddrs.ifaddrs = ifap; + while (cur) |ifa| : (cur = ifa.ifa_next) { + if (ifa.ifa_addr) |addr| { + // Skips the loopback + if ((ifa.ifa_flags & c_net_if.IFF_LOOPBACK) != 0) continue; + + const sockaddr_ptr = @as(*const c_socket.sockaddr, @ptrCast(@alignCast(addr))); + + if (sockaddr_ptr.sa_family != c_inet.AF_INET) continue; + + var addr_in = @as(*const c_netinet_in.sockaddr_in, @ptrCast(@alignCast(sockaddr_ptr))); + var ip_buf: [c_inet.INET_ADDRSTRLEN]u8 = undefined; + const ip_str = c_inet.inet_ntop(c_inet.AF_INET, &addr_in.sin_addr, &ip_buf, c_inet.INET_ADDRSTRLEN); + if (ip_str) |ip| { + try net_info_list.append(NetInfo{ + .interface_name = try allocator.dupe(u8, std.mem.span(ifa.ifa_name)), + .ipv4_addr = try allocator.dupe(u8, std.mem.span(ip)), + }); + } + } + } + + return net_info_list; +} diff --git a/src/macos/system.zig b/src/macos/system.zig new file mode 100644 index 0000000..af2b5ec --- /dev/null +++ b/src/macos/system.zig @@ -0,0 +1,127 @@ +const std = @import("std"); +const c_sysctl = @cImport(@cInclude("sys/sysctl.h")); +const c_libproc = @cImport(@cInclude("libproc.h")); + +/// Structure representing system uptime in days, hours, and minutes. +pub const SystemUptime = struct { + days: i8, + hours: i8, + minutes: i8, +}; + +pub const KernelInfo = struct { + kernel_name: []u8, + kernel_release: []u8, +}; + +// Returns the hostname. +pub fn getHostname(allocator: std.mem.Allocator) ![]u8 { + var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; + const hostnameEnv = try std.posix.gethostname(&buf); + + const hostname = try allocator.dupe(u8, hostnameEnv); + + return hostname; +} + +pub fn getLocale(allocator: std.mem.Allocator) ![]u8 { + const locale = try std.process.getEnvVarOwned(allocator, "LANG"); + return locale; +} + +/// Returns the system uptime. +/// +/// Uses `sysctl` to fetch the system boot time and calculates the elapsed time. +pub fn getSystemUptime() !SystemUptime { + const seconds_per_day: f64 = 86400.0; + const hours_per_day: f64 = 24.0; + const seconds_per_hour: f64 = 3600.0; + const seconds_per_minute: f64 = 60.0; + + var boot_time: c_libproc.struct_timeval = undefined; + var size: usize = @sizeOf(c_libproc.struct_timeval); + + var uptime_seconds: f64 = 0.0; + + var name = [_]c_int{ c_sysctl.CTL_KERN, c_sysctl.KERN_BOOTTIME }; + if (c_sysctl.sysctl(&name, name.len, &boot_time, &size, null, 0) == 0) { + const boot_seconds = @as(f64, @floatFromInt(boot_time.tv_sec)); + const now_seconds = @as(f64, @floatFromInt(std.time.timestamp())); + uptime_seconds = now_seconds - boot_seconds; + } else { + return error.UnableToGetSystemUptime; + } + + var remainig_seconds: f64 = uptime_seconds; + const days: f64 = @floor(remainig_seconds / seconds_per_day); + + remainig_seconds = (remainig_seconds / seconds_per_day) - days; + const hours = @floor(remainig_seconds * hours_per_day); + + remainig_seconds = (remainig_seconds * hours_per_day) - hours; + const minutes = @floor((remainig_seconds * seconds_per_hour) / seconds_per_minute); + + return SystemUptime{ + .days = @as(i8, @intFromFloat(days)), + .hours = @as(i8, @intFromFloat(hours)), + .minutes = @as(i8, @intFromFloat(minutes)), + }; +} + +pub fn getKernelInfo(allocator: std.mem.Allocator) !KernelInfo { + var size: usize = 0; + + // --- KERNEL NAME --- + // First call to sysctlbyname to get the size of the string + if (c_sysctl.sysctlbyname("kern.ostype", null, &size, null, 0) != 0) { + return error.FailedToGetKernelNameSize; + } + + const kernel_type: []u8 = try allocator.alloc(u8, size - 1); + errdefer allocator.free(kernel_type); + + // Second call to sysctlbyname to get the kernel name + if (c_sysctl.sysctlbyname("kern.ostype", kernel_type.ptr, &size, null, 0) != 0) { + return error.FailedToGetKernelName; + } + + // --- KERNEL RELEASE --- + // First call to sysctlbyname to get the size of the string + if (c_sysctl.sysctlbyname("kern.osrelease", null, &size, null, 0) != 0) { + return error.FailedToGetKernelReleaseSize; + } + + const os_release: []u8 = try allocator.alloc(u8, size - 1); + errdefer allocator.free(os_release); + + // Second call to sysctlbyname to get the kernel release + if (c_sysctl.sysctlbyname("kern.osrelease", os_release.ptr, &size, null, 0) != 0) { + return error.FailedToGetKernelRelease; + } + + return KernelInfo{ + .kernel_name = kernel_type, + .kernel_release = os_release, + }; +} + +pub fn getOsInfo(allocator: std.mem.Allocator) ![]u8 { + var size: usize = 0; + + // First call to sysctlbyname to get the size of the string + if (c_sysctl.sysctlbyname("kern.osproductversion", null, &size, null, 0) != 0) { + return error.FailedToGetCpuNameSize; + } + + const os_version: []u8 = try allocator.alloc(u8, size - 1); + defer allocator.free(os_version); + + // Second call to sysctlbyname to get the os version + if (c_sysctl.sysctlbyname("kern.osproductversion", os_version.ptr, &size, null, 0) != 0) { + return error.FailedToGetOsVersion; + } + + const os_info = try std.fmt.allocPrint(allocator, "macOS {s}", .{os_version}); + + return os_info; +} diff --git a/src/macos/user.zig b/src/macos/user.zig new file mode 100644 index 0000000..1df4055 --- /dev/null +++ b/src/macos/user.zig @@ -0,0 +1,31 @@ +const std = @import("std"); + +/// Returns the current logged-in uesr's username. +/// Uses the environment variable `USER`. +/// The caller is responsible for freeing the allocated memory. +pub fn getUsername(allocator: std.mem.Allocator) ![]u8 { + const username = try std.process.getEnvVarOwned(allocator, "USER"); + return username; +} + +pub fn getShell(allocator: std.mem.Allocator) ![]u8 { + const shell = try std.process.getEnvVarOwned(allocator, "SHELL"); + + var child = std.process.Child.init(&[_][]const u8{ shell, "--version" }, allocator); + + child.stdout_behavior = .Pipe; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const output = try child.stdout.?.reader().readAllAlloc(allocator, 1024 * 1024); + + _ = try child.wait(); + + return output; +} + +pub fn getTerminalName(allocator: std.mem.Allocator) ![]u8 { + const term_progrm = try std.process.getEnvVarOwned(allocator, "TERM_PROGRAM"); + return term_progrm; +} diff --git a/src/main.zig b/src/main.zig index 2515ffa..aeff5c6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,56 +9,56 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); - const kernel_info = try detection.getKernelInfo(allocator); + const kernel_info = try detection.system.getKernelInfo(allocator); try stdout.print("Kernel: {s} {s}\n", .{ kernel_info.kernel_name, kernel_info.kernel_release }); try bw.flush(); allocator.free(kernel_info.kernel_name); allocator.free(kernel_info.kernel_release); - const os_info = try detection.getOsInfo(allocator); + const os_info = try detection.system.getOsInfo(allocator); try stdout.print("OS: {s}\n", .{os_info}); try bw.flush(); allocator.free(os_info); - const username = try detection.getUsername(allocator); + const username = try detection.user.getUsername(allocator); try stdout.print("User: {s}\n", .{username}); try bw.flush(); allocator.free(username); - const hostname = try detection.getHostname(allocator); + const hostname = try detection.system.getHostname(allocator); try stdout.print("Hostname: {s}\n", .{hostname}); try bw.flush(); allocator.free(hostname); - const locale = try detection.getLocale(allocator); + const locale = try detection.system.getLocale(allocator); try stdout.print("Locale: {s}\n", .{locale}); try bw.flush(); allocator.free(locale); - const uptime = try detection.getSystemUptime(); + const uptime = try detection.system.getSystemUptime(); try stdout.print("Uptime: {} days, {} hours, {} minutes\n", .{ uptime.days, uptime.hours, uptime.minutes }); try bw.flush(); - const shell = try detection.getShell(allocator); + const shell = try detection.user.getShell(allocator); try stdout.print("Shell: {s}", .{shell}); try bw.flush(); allocator.free(shell); - const cpu_info = try detection.getCpuInfo(allocator); + const cpu_info = try detection.hardware.getCpuInfo(allocator); try stdout.print("cpu: {s} ({}) @ {d:.2} GHz\n", .{ cpu_info.cpu_name, cpu_info.cpu_cores, cpu_info.cpu_max_freq }); try bw.flush(); allocator.free(cpu_info.cpu_name); - const gpu_info = try detection.getGpuInfo(allocator); + const gpu_info = try detection.hardware.getGpuInfo(allocator); try stdout.print("gpu: {s} ({})\n", .{ gpu_info.gpu_name, gpu_info.gpu_cores }); try bw.flush(); allocator.free(gpu_info.gpu_name); - const ram_info = try detection.getRamInfo(); + const ram_info = try detection.hardware.getRamInfo(); try stdout.print("ram: {d:.2} / {d:.2} GB ({}%)\n", .{ ram_info.ram_usage, ram_info.ram_size, ram_info.ram_usage_percentage }); try bw.flush(); - const swap_info = try detection.getSwapInfo(); + const swap_info = try detection.hardware.getSwapInfo(); if (swap_info) |s| { try stdout.print("Swap: {d:.2} / {d:.2} GB ({}%)\n", .{ s.swap_usage, s.swap_size, s.swap_usage_percentage }); } else { @@ -66,16 +66,16 @@ pub fn main() !void { } try bw.flush(); - const diskInfo = try detection.getDiskSize("/"); + const diskInfo = try detection.hardware.getDiskSize("/"); try stdout.print("disk ({s}): {d:.2} / {d:.2} GB ({}%)\n", .{ diskInfo.disk_path, diskInfo.disk_usage, diskInfo.disk_size, diskInfo.disk_usage_percentage }); try bw.flush(); - const terminal_name = try detection.getTerminalName(allocator); + const terminal_name = try detection.user.getTerminalName(allocator); try stdout.print("terminal: {s}\n", .{terminal_name}); try bw.flush(); allocator.free(terminal_name); - const net_info_list = try detection.getNetInfo(allocator); + const net_info_list = try detection.network.getNetInfo(allocator); for (net_info_list.items) |n| { try stdout.print("Local IP ({s}): {s}\n", .{ n.interface_name, n.ipv4_addr }); try bw.flush();