diff --git a/build.zig b/build.zig index 3243b0a..f2d6c7d 100644 --- a/build.zig +++ b/build.zig @@ -49,6 +49,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + exe.addIncludePath(b.path("./include")); exe.root_module.addImport("vaxis", vaxis); exe.root_module.addImport("objc", objc); diff --git a/src/detect_colors.zig b/src/detect_colors.zig new file mode 100644 index 0000000..4a2c076 --- /dev/null +++ b/src/detect_colors.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +pub fn detect_dark_mode() bool { + const home_directory = detect_home_directory(); + var dark_mode = std.fs.cwd().createFile(home_directory ++ "/.dark-mode", .{ .exclusive = true }) catch |e| + switch (e) { + error.PathAlreadyExists => { + return true; + }, + else => {}, + }; + defer dark_mode.close(); + + return false; +} + +fn detect_home_directory() []const u8 { + return std.posix.getenv("HOME") orelse "/tmp"; +} diff --git a/src/macos.zig b/src/macos.zig index 68bbfe7..3f0a485 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -1,7 +1,10 @@ const objc = @import("objc"); const std = @import("std"); -const utsname = @cImport({ +const c = @cImport({ + @cInclude("sys/types.h"); @cInclude("sys/utsname.h"); + @cInclude("sys/sysctl.h"); + @cInclude("sys/time.h"); }); const OsVersion = struct { name: []const u8, @@ -34,22 +37,65 @@ pub fn macos_version(allocator: std.mem.Allocator) ![]const u8 { const koko = info.msgSend(NSOperatingSystemVersion, "operatingSystemVersion", .{}); - const buf = try std.fmt.allocPrint(allocator, " OS: macOS {s} {d}.{d}.{d}\n", .{ version_to_name(koko), koko.major, koko.minor, koko.patch }); + const buf = try std.fmt.allocPrint(allocator, ": macOS {s} {d}.{d}.{d}\n", .{ version_to_name(koko), koko.major, koko.minor, koko.patch }); return buf; } pub fn macos_kernel_version(allocator: std.mem.Allocator) ![]const u8 { - var name: utsname.struct_utsname = undefined; + var name: c.struct_utsname = undefined; - if (utsname.uname(&name) != 0) { + if (c.uname(&name) != 0) { return error.UnameFailed; } const sysname = std.mem.sliceTo(name.sysname[0..], 0); const version = std.mem.sliceTo(name.release[0..], 0); - const buf = try std.fmt.allocPrint(allocator, " Kernel: {s} {s}\n", .{ sysname, version }); + const buf = try std.fmt.allocPrint(allocator, ": {s} {s}\n", .{ sysname, version }); + + return buf; +} + +pub fn macos_cpu_info(allocator: std.mem.Allocator) ![]const u8 { + var cpu_brand: [128]u8 = undefined; + var cpu_brand_size: usize = cpu_brand.len; + var cpu_cores: c_int = 0; + var cpu_cores_size: usize = @sizeOf(c_int); + + if (c.sysctlbyname("machdep.cpu.core_count", &cpu_cores, @as([*c]usize, &cpu_cores_size), null, 0) < 0) { + return error.SysctlFailed; + } + + if (c.sysctlbyname("machdep.cpu.brand_string", &cpu_brand, &cpu_brand_size, null, 0) < 0) { + return error.SysctlFailed; + } + + // if (c.sysctlbyname("hw.cpufrequency", &)) + const buf = try std.fmt.allocPrint(allocator, ": {s} ({d} cores)\n", .{ cpu_brand[0..cpu_brand_size], cpu_cores }); + + return buf; +} + +pub fn macos_uptime(allocator: std.mem.Allocator) ![]const u8 { + var mib: [2]c.integer_t = [_]c.integer_t{ c.CTL_KERN, c.KERN_BOOTTIME }; + var boottime: c.struct_timeval = undefined; + var size: u64 = @sizeOf(c.struct_timeval); + + if (c.sysctl(&mib[0], 2, &boottime, @as([*c]usize, &size), null, 0) < 0) { + return error.SysctlFailed; + } + + var now: c.time_t = 0; + _ = c.time(&now); + + const uptime_seconds: i64 = now - boottime.tv_sec; + + const days = @divFloor(uptime_seconds, (24 * 60 * 60)); + const hours = @divFloor(@rem(uptime_seconds, (24 * 60 * 60)), (60 * 60)); + const minutes = @divFloor(@rem(uptime_seconds, (60 * 60)), 60); + + const buf = try std.fmt.allocPrint(allocator, ": {d} days, {d} hours, {d} mins\n", .{ days, hours, minutes }); return buf; } diff --git a/src/main.zig b/src/main.zig index 1fba685..486df59 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,6 +2,7 @@ const std = @import("std"); const vaxis = @import("vaxis"); const root = @import("root.zig"); const macos = @import("macos.zig"); +const sys = @import("sys.zig"); const log = std.log.scoped(.main); @@ -35,6 +36,15 @@ pub fn main() !void { defer allocator.free(os); const kernel = try macos.macos_kernel_version(allocator); defer allocator.free(kernel); + const uptime = try macos.macos_uptime(allocator); + defer allocator.free(uptime); + const cpu = try macos.macos_cpu_info(allocator); + defer allocator.free(cpu); + const current_shell = try sys.get_shell(allocator); + defer allocator.free(current_shell); + + var term_buf: [64]u8 = undefined; + const current_terminal = try sys.get_terminal(&term_buf); // try tty.anyWriter().writeAll("\x1bc"); // try tty.anyWriter().writeAll("\x1b[1;1H"); @@ -49,17 +59,66 @@ pub fn main() !void { const win = vx.window(); win.clear(); - const koko = try vx.loadImage(allocator, writer, .{ .path = "/Users/kc/Sync/Images/apple-white.png" }); + + var logo: []const u8 = "/Users/kc/Sync/Images/apple-dark.png"; + if (try is_dark_mode(allocator)) { + logo = "/Users/kc/Sync/Images/apple-white.png"; + } + + const koko = try vx.loadImage(allocator, writer, .{ .path = logo }); const dims = try koko.cellSize(win); // const cursor_row = vx.state.cursor.row; - const logo_win = win.child(.{ .y_off = 0, .x_off = 0, .width = .{ .limit = dims.cols + 1 }, .height = .{ .limit = dims.rows + 1 }, .border = .{ .where = .right, .glyphs = .single_rounded, .style = .{ .fg = .{ .index = 15 } } } }); - // const logo_win = win.child(.{ .y_off = 0, .x_off = 0 }); - const info_win = win.child(.{ .y_off = 0, .x_off = dims.cols + 1, .width = .{ .limit = 40 }, .height = .{ .limit = 20 } }); + const logo_win = win.child(.{ + .y_off = 0, + .x_off = 0, + .width = .{ + .limit = dims.cols + 1, + }, + .height = .{ + .limit = dims.rows + 1, + }, + // .border = .{ + // .where = .right, + // .glyphs = .single_rounded, + // .style = .{ + // .fg = .{ .index = 15 }, + // }, + // }, + }); + var user_buf: [64]u8 = undefined; + const user = try sys.get_user(&user_buf); + var hostname_buf: [std.posix.HOST_NAME_MAX]u8 = undefined; + const hostname = try std.posix.gethostname(&hostname_buf); - logo_win.clear(); - info_win.clear(); + // const logo_win = win.child(.{ .y_off = 0, .x_off = 0 }); + const border_locations = vaxis.Window.BorderOptions.Locations{ + .top = true, + .bottom = true, + }; + + const whoami_win = win.child(.{ + .x_off = dims.cols + 1, + }); + const info_win = win.child(.{ + .y_off = 1, + .x_off = dims.cols + 1, + .width = .{ .limit = 40 }, + .height = .{ .limit = 6 }, + .border = .{ + .where = .{ + .other = border_locations, + }, + .glyphs = .single_rounded, + .style = .{ + .fg = .{ .index = 15 }, + }, + }, + }); + + // logo_win.clear(); + // info_win.clear(); var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false }; // try tty.anyWriter().writeAll(vaxis.ctlseqs.kitty_graphics_query); @@ -69,21 +128,91 @@ pub fn main() !void { // win.hideCursor(); // win.showCursor(10, 1); + const whoami_info = [_]vaxis.Cell.Segment{ + .{ .text = user, .style = .{ + .fg = .{ + .index = 3, + }, + } }, + .{ .text = "@", .style = .{ + .fg = .{ + .index = 7, + }, + .bold = true, + } }, + .{ .text = hostname, .style = .{ + .fg = .{ + .index = 5, + }, + } }, + }; const system_info = [_]vaxis.Cell.Segment{ + .{ + .text = " OS", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, .{ .text = os }, + .{ + .text = " Kernel", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, .{ .text = kernel }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = " Kernel: lalal\n" }, - // .{ .text = "Kernel: lalal\n" }, - // .{ .text = "Kernel: lalal\n" }, + .{ + .text = " Uptime", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, + .{ .text = uptime }, + .{ + .text = " CPU", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, + .{ .text = cpu }, + .{ + .text = " Shell", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, + .{ .text = current_shell }, + .{ + .text = " Terminal", + .style = .{ + .fg = .{ + .index = 2, + }, + .bold = true, + }, + }, + .{ .text = current_terminal }, }; try koko.draw(logo_win, .{}); // try root.kitty_image_inline_copy(allocator, tty.anyWriter(), "/Users/kc/Sync/Images/apple-dark.png"); + result = try whoami_win.print(&whoami_info, .{ .wrap = .word }); + result = try info_win.print(&system_info, .{ .wrap = .word }); // printf '\e[%sA\e[9999999D' "${lines:-0}" // try tty.anyWriter().print("\x1b[{d}A\x1b[9999999D", .{winsize.rows}); @@ -98,7 +227,7 @@ pub fn main() !void { // try image.draw(win, .{}); // try vx.render(tty.anyWriter()); - win.clear(); + // win.clear(); try buf_writer.flush(); // try vx.queryTerminalSend(tty.anyWriter()); @@ -129,6 +258,26 @@ pub fn main() !void { // std.time.sleep(10000000); } +fn is_dark_mode(allocator: std.mem.Allocator) !bool { + const home_directory = detect_home_directory(); + const dark_mode_file_path = try std.fmt.allocPrint(allocator, "{s}/.dark-mode", .{home_directory}); + defer allocator.free(dark_mode_file_path); + const dark_mode = std.fs.cwd().createFile(dark_mode_file_path, .{ .exclusive = true }) catch |err| + switch (err) { + error.PathAlreadyExists => return true, + else => { + return false; + }, + }; + dark_mode.close(); + + return false; +} + +fn detect_home_directory() []const u8 { + return std.posix.getenv("HOME") orelse "/tmp"; +} + test "simple test" { var list = std.ArrayList(i32).init(std.testing.allocator); defer list.deinit(); // try commenting this out and see if zig detects the memory leak! diff --git a/src/sys.zig b/src/sys.zig new file mode 100644 index 0000000..a712d9f --- /dev/null +++ b/src/sys.zig @@ -0,0 +1,112 @@ +const std = @import("std"); + +pub fn get_shell(allocator: std.mem.Allocator) ![]const u8 { + const shell_env = std.posix.getenv("SHELL") orelse "Unknown"; + var shell_path = std.mem.splitBackwards(u8, shell_env, "/"); + + var buf: [64]u8 = undefined; + const filename = shell_path.first(); + if (std.mem.eql(u8, filename, "zsh")) { + const zsh_version = try get_zsh_version(allocator, &buf, shell_env); + // defer allocator.free(zsh_version); + + return try std.fmt.allocPrint(allocator, ": {s} {s}\n", .{ filename, zsh_version }); + } else if (std.mem.eql(u8, filename, "bash")) { + const bash_version = try get_bash_version(allocator, &buf, shell_env); + + return try std.fmt.allocPrint(allocator, ": {s}\n", .{bash_version}); + } else if (std.mem.eql(u8, filename, "bash")) { + const version = try std.process.Child.run( + .{ + .allocator = allocator, + .argv = &.{ shell_env, "--version" }, + }, + ); + defer allocator.free(version.stdout); + defer allocator.free(version.stderr); + + return try std.fmt.allocPrint(allocator, ": {s}\n", .{version.stdout}); + } + // const version = try std.process.Child.run(.{ + // .allocator = allocator, + // .argv = &.{ shell_env, &version_flag }, + // }); + // defer allocator.free(version.stdout); + // defer allocator.free(version.stderr); + + // if (std.mem.eql(u8, "zsh", shell)) { + // std.debug.print("shell: {s}\n", .{shell}); + // } + + return try std.fmt.allocPrint(allocator, ": {s}\n", .{shell_env}); +} + +pub fn get_terminal(buf: []u8) ![]const u8 { + const term_program = std.posix.getenv("TERM_PROGRAM") orelse "Unknown"; + + return try std.fmt.bufPrint(buf, ": {s}\n", .{term_program}); +} + +fn get_zsh_version(allocator: std.mem.Allocator, buf: []u8, zsh_path: []const u8) ![]const u8 { + const zsh_version = try std.fmt.bufPrint(buf, "Unknown", .{}); + const version_string = std.process.Child.run( + .{ + .allocator = allocator, + .argv = &.{ zsh_path, "--version" }, + }, + ) catch { + return zsh_version; + }; + defer allocator.free(version_string.stdout); + defer allocator.free(version_string.stderr); + + var version = std.mem.split(u8, version_string.stdout, " "); + _ = version.first(); + + return try std.fmt.bufPrint(buf, "{s}", .{version.next() orelse "Uknown"}); + // return try std.fmt.allocPrint(allocator, "{s}", .{version.next() orelse "Unknown"}); +} +fn get_bash_version(allocator: std.mem.Allocator, buf: []u8, bash_path: []const u8) ![]const u8 { + const version_string = try std.process.Child.run( + .{ + .allocator = allocator, + .argv = &.{ bash_path, "--version" }, + }, + ); + defer allocator.free(version_string.stdout); + defer allocator.free(version_string.stderr); + + var split_lines = std.mem.split(u8, version_string.stdout, "\n"); + + var version_line = std.mem.split(u8, split_lines.first(), " "); + + var i: usize = 0; + while (version_line.next()) |word| { + std.debug.print("version_line: {s}", .{word}); + if (i == 3) { + return try std.fmt.bufPrint(buf, "{s}", .{word}); + } + i += 1; + } + + return try std.fmt.bufPrint(buf, "Unknown", .{}); +} + +pub fn get_user(buf: []u8) ![]const u8 { + const user = std.posix.getenv("USER") orelse "Unknown"; + + return try std.fmt.bufPrint(buf, "{s}", .{user}); +} + +pub fn get_hostname(buf: *[std.posix.HOST_NAME_MAX]u8) ![]const u8 { + const hostname = try std.posix.gethostname(&buf); + + return try std.fmt.bufPrint(buf, hostname, .{}); +} + +test "get_bash_version" { + var buf: [8]u8 = undefined; + const bash_version = try get_bash_version(std.testing.allocator, &buf, "/run/current-system/sw/bin/bash"); + + try std.testing.expectEqualSlices(u8, "5.2.32(1)-release", bash_version); +}