diff --git a/build.zig b/build.zig index f2d6c7d..8fdd311 100644 --- a/build.zig +++ b/build.zig @@ -36,6 +36,8 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + macos.linkFramework("IOKit"); + macos.linkFramework("CoreFoundation"); // This declares intent for the library to be installed into the standard // location when the user invokes the "install" step (the default step when @@ -49,8 +51,10 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); - exe.addIncludePath(b.path("./include")); + exe.linkFramework("IOKIT"); + exe.linkFramework("CoreFoundation"); + exe.linkFramework("SystemConfiguration"); exe.root_module.addImport("vaxis", vaxis); exe.root_module.addImport("objc", objc); diff --git a/build.zig.zon b/build.zig.zon index 940dd0e..8ae9ece 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -25,7 +25,7 @@ .dependencies = .{ .vaxis = .{ .url = "https://github.com/rockorager/libvaxis/archive/refs/heads/main.tar.gz", - .hash = "12209fb0db1467d24882f4a0fca367d25d7ef7353882270ba3b76382c185332c52ae", + .hash = "12209e3e6a758023f77c35c88723209ca78f3e8f386cef27ea73ab5ea36d6f21e14f", }, .objc = .{ .url = "https://github.com/mitchellh/zig-objc/archive/refs/heads/main.tar.gz", diff --git a/src/assets/apple-white.png b/src/assets/apple-white.png new file mode 100644 index 0000000..74595db Binary files /dev/null and b/src/assets/apple-white.png differ diff --git a/src/assets/freebsd.png b/src/assets/freebsd.png new file mode 100644 index 0000000..b7583d5 Binary files /dev/null and b/src/assets/freebsd.png differ diff --git a/src/macos.zig b/src/macos.zig index 3f0a485..8af4483 100644 --- a/src/macos.zig +++ b/src/macos.zig @@ -5,6 +5,11 @@ const c = @cImport({ @cInclude("sys/utsname.h"); @cInclude("sys/sysctl.h"); @cInclude("sys/time.h"); + @cInclude("IOKit/IOKitLib.h"); + @cInclude("IOKit/ps/IOPowerSources.h"); + @cInclude("IOKit/ps/IOPSKeys.h"); + @cInclude("CoreFoundation/CoreFoundation.h"); + @cInclude("SystemConfiguration/SystemConfiguration.h"); }); const OsVersion = struct { name: []const u8, @@ -57,6 +62,122 @@ pub fn macos_kernel_version(allocator: std.mem.Allocator) ![]const u8 { return buf; } +pub fn memory_info(allocator: std.mem.Allocator) ![]const u8 { + var memsize: u64 = 0; + var memsize_size: usize = @sizeOf(u64); + + if (c.sysctlbyname("hw.memsize", &memsize, @as([*c]usize, &memsize_size), null, 0) < 0) { + return error.NoMemInfo; + } + + const bingb: u64 = (1024 * 1024 * 1024); + + const memsize_gb: u64 = @divFloor(memsize, bingb); + return try std.fmt.allocPrint(allocator, ": {d} GiB\n", .{memsize_gb}); +} + +// Helper function to create CFString from Zig string +fn CFStringCreateWithCStringZig(s: []const u8) *c.__CFString { + return @constCast(c.CFStringCreateWithCString( + null, // Allocator (default: null) + s.ptr, // C string pointer + c.kCFStringEncodingUTF8, // Encoding (UTF-8) + ).?); +} + +pub fn get_vpn_info() !void { + const infoz: []const u8 = "infoz"; + const cfstringref = CFStringCreateWithCStringZig(infoz); + const prefs = c.SCPreferencesCreate(null, cfstringref, null); + if (prefs == null) { + return error.SCPreferencesCreateError; + } + + const networkServices = c.SCNetworkServiceCopyAll(prefs); + if (networkServices == null) { + return error.SCNetworkServiceCopyAllError; + } + + const count = c.CFArrayGetCount(networkServices); + + std.debug.print("Network Services: {d}\n", .{count}); + + const zero: usize = 0; + for (zero..@as(c.u_long, @intCast(count))) |index| { + const long_index = @as(c_long, @intCast(index)); + const service: c.SCNetworkServiceRef = @ptrCast(c.CFArrayGetValueAtIndex(networkServices, long_index)); + const serviceName = c.SCNetworkServiceGetName(service); + const bufferSize = 256; + var buffer: [bufferSize]u8 = undefined; + _ = c.CFStringGetCString(serviceName, &buffer[0], bufferSize, c.kCFStringEncodingUTF8); + const service_name = std.mem.sliceTo(buffer[0..], 0); + std.debug.print("Found VPN service: {s}\n", .{service_name}); + } + + //const interface = c.SCNetworkServiceGetInterface(service); + + // Convert CFStringRef to C string (null-terminated) +} + +pub fn get_battery_info(allocator: std.mem.Allocator) ![]const u8 { + const power_sources_info: c.CFTypeRef = c.IOPSCopyPowerSourcesInfo(); + + if (power_sources_info == null) { + return error.NoPowerSourcesInfo; + } + + const power_sources_list = c.IOPSCopyPowerSourcesList(power_sources_info); + + if (power_sources_list == null) { + c.CFRelease(power_sources_info); + + return error.NoPowerSourcesList; + } + + const power_source = c.IOPSGetPowerSourceDescription(power_sources_info, c.CFArrayGetValueAtIndex(power_sources_list, 0)) orelse null; + + if (power_source == null) { + return error.GotNoPowerSource; + } + const name: c.CFStringRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSNameKey))); + const power_type: c.CFStringRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSTypeKey))); + const capacity: c.CFNumberRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSCurrentCapacityKey))); + const max_capacity: c.CFNumberRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSMaxCapacityKey))); + const source_state: c.CFStringRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSPowerSourceStateKey))); + const time_to_empty: c.CFNumberRef = @ptrCast(c.CFDictionaryGetValue(power_source, CFStringCreateWithCStringZig(c.kIOPSTimeToEmptyKey))); + + var name_buffer: [256]u8 = undefined; + var power_type_buffer: [256]u8 = undefined; + var power_source_state_buffer: [256]u8 = undefined; + var maxCapacity: c.u_int32_t = 0; + var currentCapacity: c.u_int32_t = 0; + var time_to_empty_mins: c.u_int32_t = 0; + _ = c.CFNumberGetValue(capacity, c.kCFNumberIntType, ¤tCapacity); + _ = c.CFNumberGetValue(max_capacity, c.kCFNumberIntType, &maxCapacity); + _ = c.CFNumberGetValue(time_to_empty, c.kCFNumberIntType, &time_to_empty_mins); + + _ = c.CFStringGetCString(name, &name_buffer[0], 256, c.kCFStringEncodingUTF8); + + _ = c.CFStringGetCString(power_type, &power_type_buffer[0], 256, c.kCFStringEncodingUTF8); + + _ = c.CFStringGetCString(source_state, &power_source_state_buffer[0], 256, c.kCFStringEncodingUTF8); + const power_source_state = std.mem.sliceTo(power_source_state_buffer[0..], 0); + + const percent = (currentCapacity * 100) / maxCapacity; + const hours = time_to_empty_mins / 60; + const minutes = time_to_empty_mins % 60; + + if (std.mem.eql(u8, power_source_state, "Battery Power")) { + if (time_to_empty_mins < 0) { + return try std.fmt.allocPrint(allocator, ": {d}%, ?m left\n", .{ percent, hours, minutes }); + } else { + return try std.fmt.allocPrint(allocator, ": {d}%, {d}h:{d}m left\n", .{ percent, hours, minutes }); + } + } else { + return try std.fmt.allocPrint(allocator, ": {d}%, {s}\n", .{ percent, "charging" }); + } +} + 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; diff --git a/src/main.zig b/src/main.zig index 4d1c9b3..f32201b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const vaxis = @import("vaxis"); const root = @import("root.zig"); const macos = @import("macos.zig"); @@ -6,6 +7,9 @@ const sys = @import("sys.zig"); const log = std.log.scoped(.main); +const apple_logo = @embedFile("assets/apple-white.png"); +const freebsd = @embedFile("assets/freebsd.png"); + const Event = union(enum) { key_press: vaxis.Key, winsize: vaxis.Winsize, @@ -32,8 +36,13 @@ pub fn main() !void { vx.caps.unicode = .unicode; vx.caps.kitty_graphics = true; - const os = try macos.macos_version(allocator); - defer allocator.free(os); + const battery = try macos.get_battery_info(allocator); + defer allocator.free(battery); + // _ = try macos.get_vpn_info(); + + // const os = try macos.macos_version(allocator); + const os = try sys.get_os_version(allocator); + defer allocator.free(os.text); const kernel = try macos.macos_kernel_version(allocator); defer allocator.free(kernel); const uptime = try macos.macos_uptime(allocator); @@ -42,36 +51,43 @@ pub fn main() !void { defer allocator.free(cpu); const current_shell = try sys.get_shell(allocator); defer allocator.free(current_shell); + const memory = try macos.memory_info(allocator); + defer allocator.free(memory); 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"); - try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); - defer vx.queryTerminalSend(tty.anyWriter()) catch {}; - defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; - var buf_writer = tty.bufferedWriter(); const writer = buf_writer.writer().any(); + // try writer.writeAll(vaxis.ctlseqs.unicode_set); + // defer vx.queryTerminalSend(writer) catch {}; + // defer writer.writeAll(vaxis.ctlseqs.unicode_reset) catch {}; + // defer tty.deinit(); const winsize: vaxis.Winsize = try vaxis.Tty.getWinsize(tty.fd); - try vx.resize(allocator, tty.anyWriter(), winsize); + try vx.resize(allocator, writer, winsize); const win = vx.window(); win.clear(); + // win.hideCursor(); - 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"; + var image: vaxis.Image = undefined; + switch (builtin.os.tag) { + .macos => { + image = try vx.loadImage(allocator, tty.anyWriter(), .{ .mem = apple_logo }); + }, + .freebsd => { + image = try vx.loadImage(allocator, writer, .{ .mem = freebsd }); + }, + else => @compileError("unsupported os"), } - const koko = try vx.loadImage(allocator, writer, .{ .path = logo }); - - const dims = try koko.cellSize(win); + const dims = try image.cellSize(win); // const cursor_row = vx.state.cursor.row; const logo_win = win.child(.{ - .y_off = 0, + .y_off = 2, .x_off = 0, .width = .{ .limit = dims.cols + 1, @@ -79,15 +95,8 @@ pub fn main() !void { .height = .{ .limit = dims.rows + 1, }, - // .border = .{ - // .where = .right, - // .glyphs = .single_rounded, - // .style = .{ - // .fg = .{ .index = 15 }, - // }, - // }, }); - // const logo_text_win = win.child(.{ .y_off = dims.rows }); + var user_buf: [64]u8 = undefined; const user = try sys.get_user(&user_buf); var hostname_buf: [std.posix.HOST_NAME_MAX]u8 = undefined; @@ -96,7 +105,7 @@ pub fn main() !void { // const logo_win = win.child(.{ .y_off = 0, .x_off = 0 }); const border_locations = vaxis.Window.BorderOptions.Locations{ .top = true, - .bottom = true, + .bottom = false, }; const whoami_win = win.child(.{ @@ -106,7 +115,7 @@ pub fn main() !void { .y_off = 1, .x_off = dims.cols + 1, .width = .{ .limit = 40 }, - .height = .{ .limit = 10 }, + .height = .{ .limit = 16 }, .border = .{ .where = .{ .other = border_locations, @@ -118,32 +127,23 @@ pub fn main() !void { }, }); - // 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); - // var img1 = try vaxis.zigimg.Image.fromFilePath(allocator, "/Users/kc/Sync/Images/apple-white.png"); - // defer img1.deinit(); - // const koko = try vx.transmitImage(allocator, tty.anyWriter(), &img1, .png); - // win.hideCursor(); - // win.showCursor(10, 1); const whoami_info = [_]vaxis.Cell.Segment{ .{ .text = user, .style = .{ .fg = .{ - .index = 3, + .index = 7, }, } }, .{ .text = "@", .style = .{ .fg = .{ - .index = 7, + .index = 15, }, .bold = true, } }, .{ .text = hostname, .style = .{ .fg = .{ - .index = 5, + .index = 7, }, } }, }; @@ -157,7 +157,7 @@ pub fn main() !void { .bold = true, }, }, - .{ .text = os }, + os, .{ .text = " Kernel", .style = .{ @@ -188,6 +188,16 @@ pub fn main() !void { }, }, .{ .text = cpu }, + .{ + .text = " Memory", + .style = .{ + .fg = .{ + .index = 7, + }, + .bold = true, + }, + }, + .{ .text = memory }, .{ .text = " Shell", .style = .{ @@ -208,6 +218,23 @@ pub fn main() !void { }, }, .{ .text = current_terminal }, + .{ + .text = " Battery", + .style = .{ + .fg = .{ + .index = 7, + }, + .bold = true, + }, + }, + .{ + .text = battery, + // .style = .{ + // .fg = .{ + // .index = 2, + // }, + // }, + }, .{ .text = " ▂▂▂", .style = .{ @@ -329,20 +356,23 @@ pub fn main() !void { }, }, .{ - .text = " ▂▂▂", + .text = " ▂▂▂\n\n\n", .style = .{ .fg = .{ .index = 15, }, }, }, + .{ + .text = " ", + }, }; // const logo_text = [_]vaxis.Cell.Segment{ // .{ // .text = " hello", // }, // }; - try koko.draw(logo_win, .{}); + try image.draw(logo_win, .{}); // _ = try logo_text_win.print(&logo_text, .{}); // try root.kitty_image_inline_copy(allocator, tty.anyWriter(), "/Users/kc/Sync/Images/apple-dark.png"); @@ -353,7 +383,7 @@ pub fn main() !void { // try tty.anyWriter().print("\x1b[{d}A\x1b[9999999D", .{winsize.rows}); // try tty.anyWriter().writeAll("\x1b[1J"); - try vx.prettyPrint(writer); + // try logo_win.print(); // const dimensions = try koko.cellSize(win); // const image = try vx.transmitLocalImagePath(allocator, tty.anyWriter(), "/Users/kc/Sync/Images/apple-white.png", dimensions.rows, dimensions.cols, .file, .png); @@ -362,8 +392,9 @@ pub fn main() !void { // try image.draw(win, .{}); // try vx.render(tty.anyWriter()); - // win.clear(); + try vx.prettyPrint(tty.anyWriter()); try buf_writer.flush(); + // try vx.queryTerminalSend(tty.anyWriter()); // Clear from cursor to beginning of the screen @@ -381,7 +412,6 @@ pub fn main() !void { // // vaxis.Image.DrawOptions{ .clip_region = .{ .x = 0, .y = 0 } }, // }; - // win.hideCursor(); // const dims = try image[0].cellSize(win); // try vx.render(writer); diff --git a/src/sys.zig b/src/sys.zig index a712d9f..0cb48c3 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -1,4 +1,20 @@ const std = @import("std"); +const builtin = @import("builtin"); +const vaxis = @import("vaxis"); + +const macos = @import("macos.zig"); + +const OSVersion = struct { + name: []const u8, + major: []u64, + minor: []u64, + patch: []u64, +}; + +const Shell = struct { + name: []const u8, + version: []const u8, +}; pub fn get_shell(allocator: std.mem.Allocator) ![]const u8 { const shell_env = std.posix.getenv("SHELL") orelse "Unknown"; @@ -14,7 +30,7 @@ pub fn get_shell(allocator: std.mem.Allocator) ![]const u8 { } 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}); + return try std.fmt.allocPrint(allocator, ": bash {s}\n", .{bash_version}); } else if (std.mem.eql(u8, filename, "bash")) { const version = try std.process.Child.run( .{ @@ -27,23 +43,28 @@ pub fn get_shell(allocator: std.mem.Allocator) ![]const u8 { 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_os_version(allocator: std.mem.Allocator) !vaxis.Cell.Segment { + switch (builtin.os.tag) { + .macos => { + return .{ + .text = try macos.macos_version(allocator), + }; + }, + else => return .{ .text = "" }, + } +} + pub fn get_terminal(buf: []u8) ![]const u8 { const term_program = std.posix.getenv("TERM_PROGRAM") orelse "Unknown"; + if (std.mem.eql(u8, term_program, "ghostty")) { + return try std.fmt.bufPrint(buf, ": {s} 󰊠\n", .{term_program}); + } + return try std.fmt.bufPrint(buf, ": {s}\n", .{term_program}); }