From f4f056f991a9147ab3e5036833ce4d8d06fda96f Mon Sep 17 00:00:00 2001 From: Zhongheng Liu Date: Tue, 28 Apr 2026 17:38:57 +0200 Subject: [PATCH] Custom command parsing from config file and handling --- src/Bar.zig | 18 +++++++++++++----- src/config/BarConfig.zig | 10 ++++++++++ src/utils.zig | 27 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/Bar.zig b/src/Bar.zig index 02bfc48..d5c4b40 100644 --- a/src/Bar.zig +++ b/src/Bar.zig @@ -44,7 +44,7 @@ const PendingRender = struct { }; pub const Position = enum { top, bottom }; -pub const Component = enum { title, clock, wm_info, none }; +pub const Component = enum { title, clock, wm_info, custom, none }; pub const Options = struct { /// Comma separated list of FontConfig formatted font specifications @@ -67,6 +67,8 @@ pub const Options = struct { /// strftime format string for the clock display time_format: []const u8 = default_time_format, + /// Custom command to execute for the custom display + custom_command: []const u8 = "uname -r", /// Which component to show on the left side of the bar left: Component = .title, /// Which component to show in the center of the bar @@ -272,25 +274,31 @@ fn draw(bar: *Bar) !void { const wm_info_codepoints = try utils.utf8ToCodepoints(wm_info_writer.buffered()); defer utils.gpa.free(wm_info_codepoints); + // Processing custom component + const argv = [_][]const u8{ "sh", "-c", options.custom_command}; + const custom_str = try utils.execCommand(utils.gpa, &argv); + const custom_codepoints = try utils.utf8ToCodepoints(custom_str); + defer utils.gpa.free(custom_codepoints); // Map a Component to its pre-computed codepoints slice const componentSlice = struct { - fn f(component: Component, clock: []u32, title: []u32, wm_info: []u32) []u32 { + fn f(component: Component, clock: []u32, title: []u32, wm_info: []u32, custom: []u32) []u32 { return switch (component) { .clock => clock, .title => title, .wm_info => wm_info, + .custom => custom, .none => &.{}, }; } }.f; // Measure center first; needed to constrain left and right widths - const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints); + const center_codepoints = componentSlice(options.center, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints); const center_width = try bar.textWidth(center_codepoints); var center_x: i32 = @divFloor(buffer.width - center_width, 2); // Render left slot - const left_codepoints = componentSlice(options.left, clock_codepoints, title_codepoints, wm_info_codepoints); + const left_codepoints = componentSlice(options.left, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints); if (left_codepoints.len > 0) { const max_width = center_x - 2 * options.horizontal_padding; const truncated = try bar.truncateToWidth(left_codepoints, max_width); @@ -299,7 +307,7 @@ fn draw(bar: *Bar) !void { } // Render right slot - const right_codepoints = componentSlice(options.right, clock_codepoints, title_codepoints, wm_info_codepoints); + const right_codepoints = componentSlice(options.right, clock_codepoints, title_codepoints, wm_info_codepoints, custom_codepoints); if (right_codepoints.len > 0) { const max_width = buffer.width - (center_x + center_width) - 2 * options.horizontal_padding; const truncated = try bar.truncateToWidth(right_codepoints, max_width); diff --git a/src/config/BarConfig.zig b/src/config/BarConfig.zig index ea7179c..890fd67 100644 --- a/src/config/BarConfig.zig +++ b/src/config/BarConfig.zig @@ -16,6 +16,7 @@ const NodeName = enum { horizontal_padding, margins, time_format, + custom_command, }; const MarginsNodeName = enum { top, right, bottom, left }; @@ -52,6 +53,7 @@ horizontal_padding: u8 = 5, /// strftime format string for the clock display. /// null means use the default. time_format: ?[]const u8 = null, +custom_command: ?[]const u8 = null, pub fn toBarOptions(config: BarConfig) Bar.Options { return .{ @@ -68,6 +70,7 @@ pub fn toBarOptions(config: BarConfig) Bar.Options { .bottom = config.margin_bottom, .left = config.margin_left, }, + .custom_command = config.custom_command orelse "uname -r", .vertical_padding = config.vertical_padding, .horizontal_padding = config.horizontal_padding, .time_format = config.time_format orelse Bar.default_time_format, @@ -123,6 +126,13 @@ pub fn load(config: *Config, parser: *kdl.Parser, hostname: ?[]const u8) !void { logWarnInvalidNodeArg(name, val_str); } }, + .custom_command => { + if (node.argcount() < 1) { + logWarnMissingNodeArg(name, "custom command"); + continue; + } + config.bar_config.?.custom_command = utils.gpa.dupe(u8, val_str) catch @panic("Out of memory"); + }, .margins => next_child_block = .margins, inline .background_color, .text_color, diff --git a/src/utils.zig b/src/utils.zig index 0718ed2..b3611e5 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -227,6 +227,33 @@ pub fn versionNotSupported(comptime WaylandGlobal: type, have_version: u32, need fatal("The compositor only advertised {s} version {d} but version {d} is required. Exiting", .{ WaylandGlobal.interface.name, have_version, need_version }); } +pub fn execCommand(allocator: std.mem.Allocator, argv: []const []const u8) ![]u8 { + // .run() spawns the process, collects stdout/stderr, and waits for completion. + // We set a max_output_size (e.g., 50KB) to prevent runaway memory usage. + const result = try std.process.Child.run(.{ + .allocator = allocator, + .argv = argv, + .max_output_bytes = 50 * 1024, + }); + + // We don't need stderr for this plain function, so free it immediately. + allocator.free(result.stderr); + + // If the command failed (non-zero exit), you might want to handle that. + switch (result.term) { + .Exited => |code| if (code != 0) { + allocator.free(result.stdout); + return error.CommandFailed; + }, + else => { + allocator.free(result.stdout); + return error.CommandTerminatedAbnormally; + }, + } + + return result.stdout; +} + const std = @import("std"); const fatal = std.process.fatal; const fmt = std.fmt;