- commit
- 622a781
- parent
- 0dd41e3
- author
- Eric Bower
- date
- 2025-10-11 15:53:37 -0400 EDT
feat: config toml
9 files changed,
+193,
-39
+6,
-0
1@@ -38,6 +38,12 @@ pub fn build(b: *std.Build) void {
2 });
3 exe_mod.addImport("clap", clap_dep.module("clap"));
4
5+ const toml_dep = b.dependency("toml", .{
6+ .target = target,
7+ .optimize = optimize,
8+ });
9+ exe_mod.addImport("toml", toml_dep.module("toml"));
10+
11 // Exe
12 const exe = b.addExecutable(.{
13 .name = "zmx",
+4,
-0
1@@ -44,6 +44,10 @@
2 .url = "git+https://github.com/Hejsil/zig-clap?ref=HEAD#b7e3348ed60f99ba32c75aa707ff7c87adc31b36",
3 .hash = "clap-0.11.0-oBajB-TnAQC7yPLnZRT5WzHZ_4Ly4dX2OILskli74b9H",
4 },
5+ .toml = .{
6+ .url = "git+https://github.com/sam701/zig-toml#3aaeb0bf14935eabae118196f7949b404a42f8ea",
7+ .hash = "toml-0.3.0-bV14BTd8AQA-wZERtB3dvRE3eSZ-m48AyXFUGkZ_Tm3d",
8+ },
9 },
10 .paths = .{
11 "build.zig",
+26,
-17
1@@ -2,7 +2,7 @@ const std = @import("std");
2 const posix = std.posix;
3 const xevg = @import("xev");
4 const xev = xevg.Dynamic;
5-const socket_path = "/tmp/zmx.sock";
6+const clap = @import("clap");
7
8 const c = @cImport({
9 @cInclude("termios.h");
10@@ -22,21 +22,37 @@ const Context = struct {
11 read_ctx: ?*ReadContext = null,
12 };
13
14-pub fn main() !void {
15+const params = clap.parseParamsComptime(
16+ \\-s, --socket-path <str> Path to the Unix socket file
17+ \\<str>
18+ \\
19+);
20+
21+pub fn main(socket_path_default: []const u8, iter: *std.process.ArgIterator) !void {
22 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
23 defer _ = gpa.deinit();
24 const allocator = gpa.allocator();
25
26- // Get session name from command-line arguments
27- const args = try std.process.argsAlloc(allocator);
28- defer std.process.argsFree(allocator, args);
29+ var diag = clap.Diagnostic{};
30+ var res = clap.parseEx(clap.Help, ¶ms, clap.parsers.default, iter, .{
31+ .diagnostic = &diag,
32+ .allocator = allocator,
33+ }) catch |err| {
34+ var buf: [1024]u8 = undefined;
35+ var stderr_file = std.fs.File{ .handle = posix.STDERR_FILENO };
36+ var writer = stderr_file.writer(&buf);
37+ diag.report(&writer.interface, err) catch {};
38+ writer.interface.flush() catch {};
39+ return err;
40+ };
41+ defer res.deinit();
42+
43+ const socket_path = res.args.@"socket-path" orelse socket_path_default;
44
45- if (args.len < 3) {
46+ const session_name = res.positionals[0] orelse {
47 std.debug.print("Usage: zmx attach <session-name>\n", .{});
48 std.process.exit(1);
49- }
50-
51- const session_name = args[2];
52+ };
53
54 var thread_pool = xevg.ThreadPool.init(.{});
55 defer thread_pool.deinit();
56@@ -57,15 +73,10 @@ pub fn main() !void {
57 posix.connect(socket_fd, &unix_addr.any, unix_addr.getOsSockLen()) catch |err| {
58 if (err == error.ConnectionRefused) {
59 std.debug.print("Error: Unable to connect to zmx daemon at {s}\nPlease start the daemon first with: zmx daemon\n", .{socket_path});
60- std.process.exit(1);
61 }
62- return err;
63+ std.process.exit(1);
64 };
65
66- _ = posix.write(posix.STDERR_FILENO, "Attaching to session: ") catch {};
67- _ = posix.write(posix.STDERR_FILENO, session_name) catch {};
68- _ = posix.write(posix.STDERR_FILENO, "\n") catch {};
69-
70 // Set raw mode after successful connection
71 defer _ = c.tcsetattr(posix.STDIN_FILENO, c.TCSANOW, &orig_termios);
72
73@@ -234,8 +245,6 @@ fn readCallback(
74 } else if (std.mem.eql(u8, msg_type, "pty_out")) {
75 const text = payload.get("text").?.string;
76 _ = posix.write(posix.STDOUT_FILENO, text) catch {};
77- } else {
78- std.debug.print("Unknown message type: {s}\n", .{msg_type});
79 }
80
81 return .rearm;
+49,
-0
1@@ -0,0 +1,49 @@
2+const std = @import("std");
3+const toml = @import("toml");
4+
5+pub const Config = struct {
6+ socket_path: []const u8 = "/tmp/zmx.sock",
7+
8+ pub fn load(allocator: std.mem.Allocator) !Config {
9+ const config_path = getConfigPath(allocator) catch |err| {
10+ if (err == error.FileNotFound) {
11+ return Config{};
12+ }
13+ return err;
14+ };
15+ defer allocator.free(config_path);
16+
17+ var parser = toml.Parser(Config).init(allocator);
18+ defer parser.deinit();
19+
20+ var result = parser.parseFile(config_path) catch |err| {
21+ if (err == error.FileNotFound) {
22+ return Config{};
23+ }
24+ return err;
25+ };
26+ defer result.deinit();
27+
28+ const config = Config{
29+ .socket_path = try allocator.dupe(u8, result.value.socket_path),
30+ };
31+ return config;
32+ }
33+
34+ pub fn deinit(self: *Config, allocator: std.mem.Allocator) void {
35+ if (!std.mem.eql(u8, self.socket_path, "/tmp/zmx.sock")) {
36+ allocator.free(self.socket_path);
37+ }
38+ }
39+};
40+
41+fn getConfigPath(allocator: std.mem.Allocator) ![]const u8 {
42+ const home = std.posix.getenv("HOME") orelse return error.FileNotFound;
43+ const xdg_config_home = std.posix.getenv("XDG_CONFIG_HOME");
44+
45+ if (xdg_config_home) |config_home| {
46+ return try std.fs.path.join(allocator, &.{ config_home, "zmx", "config.toml" });
47+ } else {
48+ return try std.fs.path.join(allocator, &.{ home, ".config", "zmx", "config.toml" });
49+ }
50+}
+29,
-3
1@@ -2,7 +2,7 @@ const std = @import("std");
2 const posix = std.posix;
3 const xevg = @import("xev");
4 const xev = xevg.Dynamic;
5-const socket_path = "/tmp/zmx.sock";
6+const clap = @import("clap");
7
8 const ghostty = @import("ghostty-vt");
9
10@@ -94,11 +94,32 @@ const ServerContext = struct {
11 allocator: std.mem.Allocator,
12 };
13
14-pub fn main() !void {
15+const params = clap.parseParamsComptime(
16+ \\-s, --socket-path <str> Path to the Unix socket file
17+ \\
18+);
19+
20+pub fn main(socket_path_default: []const u8, iter: *std.process.ArgIterator) !void {
21 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
22 defer _ = gpa.deinit();
23 const allocator = gpa.allocator();
24
25+ var diag = clap.Diagnostic{};
26+ var res = clap.parseEx(clap.Help, ¶ms, clap.parsers.default, iter, .{
27+ .diagnostic = &diag,
28+ .allocator = allocator,
29+ }) catch |err| {
30+ var buf: [1024]u8 = undefined;
31+ var stderr_file = std.fs.File{ .handle = posix.STDERR_FILENO };
32+ var writer = stderr_file.writer(&buf);
33+ diag.report(&writer.interface, err) catch {};
34+ writer.interface.flush() catch {};
35+ return err;
36+ };
37+ defer res.deinit();
38+
39+ const socket_path = res.args.@"socket-path" orelse socket_path_default;
40+
41 var thread_pool = xevg.ThreadPool.init(.{});
42 defer thread_pool.deinit();
43 defer thread_pool.shutdown();
44@@ -107,6 +128,8 @@ pub fn main() !void {
45 defer loop.deinit();
46
47 std.debug.print("zmx daemon starting...\n", .{});
48+ std.debug.print("Configuration:\n", .{});
49+ std.debug.print(" socket_path: {s}\n", .{socket_path});
50
51 _ = std.fs.cwd().deleteFile(socket_path) catch {};
52
53@@ -114,7 +137,10 @@ pub fn main() !void {
54 // SOCK.STREAM: Reliable, bidirectional communication for JSON protocol messages
55 // SOCK.NONBLOCK: Prevents blocking to work with libxev's async event loop
56 const server_fd = try posix.socket(posix.AF.UNIX, posix.SOCK.STREAM | posix.SOCK.NONBLOCK, 0);
57- defer posix.close(server_fd);
58+ defer {
59+ posix.close(server_fd);
60+ std.fs.cwd().deleteFile(socket_path) catch {};
61+ }
62
63 var unix_addr = std.net.Address.initUnix(socket_path) catch |err| {
64 std.debug.print("initUnix failed: {s}\n", .{@errorName(err)});
+24,
-4
1@@ -1,13 +1,33 @@
2 const std = @import("std");
3 const posix = std.posix;
4+const clap = @import("clap");
5
6-const socket_path = "/tmp/zmx.sock";
7+const params = clap.parseParamsComptime(
8+ \\-s, --socket-path <str> Path to the Unix socket file
9+ \\
10+);
11
12-pub fn main() !void {
13+pub fn main(socket_path_default: []const u8, iter: *std.process.ArgIterator) !void {
14 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
15 defer _ = gpa.deinit();
16 const allocator = gpa.allocator();
17
18+ var diag = clap.Diagnostic{};
19+ var res = clap.parseEx(clap.Help, ¶ms, clap.parsers.default, iter, .{
20+ .diagnostic = &diag,
21+ .allocator = allocator,
22+ }) catch |err| {
23+ var buf: [1024]u8 = undefined;
24+ var stderr_file = std.fs.File{ .handle = posix.STDERR_FILENO };
25+ var writer = stderr_file.writer(&buf);
26+ diag.report(&writer.interface, err) catch {};
27+ writer.interface.flush() catch {};
28+ return err;
29+ };
30+ defer res.deinit();
31+
32+ const socket_path = res.args.@"socket-path" orelse socket_path_default;
33+
34 // Find the client_fd file in home directory
35 const home_dir = posix.getenv("HOME") orelse "/tmp";
36
37@@ -21,8 +41,8 @@ pub fn main() !void {
38 };
39 defer dir.close();
40
41- var iter = dir.iterate();
42- while (iter.next() catch null) |entry| {
43+ var dir_iter = dir.iterate();
44+ while (dir_iter.next() catch null) |entry| {
45 if (entry.kind != .file) continue;
46 if (!std.mem.startsWith(u8, entry.name, ".zmx_client_fd_")) continue;
47
+24,
-8
1@@ -1,22 +1,38 @@
2 const std = @import("std");
3 const posix = std.posix;
4+const clap = @import("clap");
5
6-const socket_path = "/tmp/zmx.sock";
7+const params = clap.parseParamsComptime(
8+ \\-s, --socket-path <str> Path to the Unix socket file
9+ \\<str>
10+ \\
11+);
12
13-pub fn main() !void {
14+pub fn main(socket_path_default: []const u8, iter: *std.process.ArgIterator) !void {
15 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
16 defer _ = gpa.deinit();
17 const allocator = gpa.allocator();
18
19- const args = try std.process.argsAlloc(allocator);
20- defer std.process.argsFree(allocator, args);
21+ var diag = clap.Diagnostic{};
22+ var res = clap.parseEx(clap.Help, ¶ms, clap.parsers.default, iter, .{
23+ .diagnostic = &diag,
24+ .allocator = allocator,
25+ }) catch |err| {
26+ var buf: [1024]u8 = undefined;
27+ var stderr_file = std.fs.File{ .handle = posix.STDERR_FILENO };
28+ var writer = stderr_file.writer(&buf);
29+ diag.report(&writer.interface, err) catch {};
30+ writer.interface.flush() catch {};
31+ return err;
32+ };
33+ defer res.deinit();
34+
35+ const socket_path = res.args.@"socket-path" orelse socket_path_default;
36
37- if (args.len < 3) {
38+ const session_name = res.positionals[0] orelse {
39 std.debug.print("Usage: zmx kill <session-name>\n", .{});
40 std.process.exit(1);
41- }
42-
43- const session_name = args[2];
44+ };
45
46 const unix_addr = try std.net.Address.initUnix(socket_path);
47 const socket_fd = try posix.socket(posix.AF.UNIX, posix.SOCK.STREAM, 0);
+22,
-2
1@@ -1,13 +1,33 @@
2 const std = @import("std");
3 const posix = std.posix;
4+const clap = @import("clap");
5
6-const socket_path = "/tmp/zmx.sock";
7+const params = clap.parseParamsComptime(
8+ \\-s, --socket-path <str> Path to the Unix socket file
9+ \\
10+);
11
12-pub fn main() !void {
13+pub fn main(socket_path_default: []const u8, iter: *std.process.ArgIterator) !void {
14 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
15 defer _ = gpa.deinit();
16 const allocator = gpa.allocator();
17
18+ var diag = clap.Diagnostic{};
19+ var res = clap.parseEx(clap.Help, ¶ms, clap.parsers.default, iter, .{
20+ .diagnostic = &diag,
21+ .allocator = allocator,
22+ }) catch |err| {
23+ var buf: [1024]u8 = undefined;
24+ var stderr_file = std.fs.File{ .handle = posix.STDERR_FILENO };
25+ var writer = stderr_file.writer(&buf);
26+ diag.report(&writer.interface, err) catch {};
27+ writer.interface.flush() catch {};
28+ return err;
29+ };
30+ defer res.deinit();
31+
32+ const socket_path = res.args.@"socket-path" orelse socket_path_default;
33+
34 const unix_addr = try std.net.Address.initUnix(socket_path);
35 const socket_fd = try posix.socket(posix.AF.UNIX, posix.SOCK.STREAM, 0);
36 defer posix.close(socket_fd);
+9,
-5
1@@ -1,5 +1,6 @@
2 const std = @import("std");
3 const cli = @import("cli.zig");
4+const config_mod = @import("config.zig");
5 const daemon = @import("daemon.zig");
6 const attach = @import("attach.zig");
7 const detach = @import("detach.zig");
8@@ -29,12 +30,15 @@ pub fn main() !void {
9 return;
10 };
11
12+ var config = try config_mod.Config.load(allocator);
13+ defer config.deinit(allocator);
14+
15 switch (command) {
16 .help => try cli.help(),
17- .daemon => try daemon.main(),
18- .list => try list.main(),
19- .attach => try attach.main(),
20- .detach => try detach.main(),
21- .kill => try kill.main(),
22+ .daemon => try daemon.main(config.socket_path, &iter),
23+ .list => try list.main(config.socket_path, &iter),
24+ .attach => try attach.main(config.socket_path, &iter),
25+ .detach => try detach.main(config.socket_path, &iter),
26+ .kill => try kill.main(config.socket_path, &iter),
27 }
28 }