diff --git a/src/Config.zig b/src/Config.zig new file mode 100644 index 0000000..7acac14 --- /dev/null +++ b/src/Config.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +const Config = @This(); + +elevation_mask: f64 = 19, +rotctld: AddressPort = .{ .address = "127.0.0.1", .port = 4533 }, +listen: AddressPort = .{ .address = "127.0.0.1", .port = 42069 }, + +const confdir = "rotint"; +const conffile = "config.json"; + +pub fn default() Config { + return .{}; +} + +pub fn load(allocator: std.mem.Allocator, env: *const std.process.EnvMap) !Config { + if (env.get("XDG_CONFIG_HOME")) |cfg| + if (cfg.len > 0) { + return try loadPath(allocator, try std.fs.path.join( + allocator, + &.{ cfg, confdir, conffile }, + )); + }; + + if (env.get("HOME")) |home| + if (home.len > 0) { + return try loadPath(allocator, try std.fs.path.join( + allocator, + &.{ home, ".config", confdir, conffile }, + )); + }; + + return error.ConfigHomeMissing; +} + +fn loadPath(allocator: std.mem.Allocator, path: []const u8) !Config { + defer allocator.free(path); + const data = try std.fs.cwd().readFileAlloc(allocator, path, 1024); + defer allocator.free(data); + + return try std.json.parseFromSliceLeaky(Config, allocator, data, .{}); +} + +const AddressPort = struct { + address: []const u8, + port: u16, +}; diff --git a/src/main.zig b/src/main.zig index 09b96fc..bc6e4cf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,7 @@ const xev = @import("xev"); const networking = @import("./networking.zig"); const rotctl = @import("./rotctl.zig"); +const Config = @import("./Config.zig"); const log = std.log.scoped(.rotint); @@ -84,6 +85,7 @@ pub const RotInt = struct { command_freq: u8 = 4, pollcount: u9 = 0, state: State = .initial, + conf: Config, termbuffer: std.io.BufferedWriter(4096, std.io.AnyWriter), vx: *vaxis.Vaxis, @@ -110,8 +112,18 @@ pub const RotInt = struct { self.rotator.rotint = self; self.log = try Log.init(); - const connect_addr = try std.net.Address.parseIp("127.0.0.1", 4533); - try self.rotator.connect(self.loop, connect_addr); + const rotctld_addr = std.net.Address.parseIp( + self.conf.rotctld.address, + self.conf.rotctld.port, + ) catch |err| { + log.err("could not parse rotctld address {s}:{d}: {s}", .{ + self.conf.rotctld.address, + self.conf.rotctld.port, + @errorName(err), + }); + return err; + }; + try self.rotator.connect(self.loop, rotctld_addr); } pub fn stateEvent(self: *RotInt, event: State) void { @@ -126,12 +138,15 @@ pub const RotInt = struct { }, .rotator_ready => if (self.state == .rotator_connected) { self.warn("rotator ready", .{}); - const listen_addr = std.net.Address.parseIp("127.0.0.1", 42069) catch { - self.warn("bogus listen address", .{}); + const listen_addr = std.net.Address.parseIp( + self.conf.listen.address, + self.conf.listen.port, + ) catch |err| { + self.showError("bogus listen address: {s}", .{@errorName(err)}); return; }; - self.server.listen(self.loop, listen_addr) catch { - self.warn("listen problem", .{}); + self.server.listen(self.loop, listen_addr) catch |err| { + self.showError("listen problem: {s}", .{@errorName(err)}); return; }; // demangle here to avoid causing initial moves @@ -209,6 +224,10 @@ pub const RotInt = struct { log.warn(fmt, args); } + pub fn showError(_: *RotInt, comptime fmt: []const u8, args: anytype) void { + log.err(fmt, args); + } + pub fn handleControlRequest(self: *RotInt, req: []const u8) void { const command = self.parser.parseCommand(req) catch |err| switch (err) { error.Incomplete => return, @@ -387,8 +406,15 @@ pub fn main() !void { }); defer loop.deinit(); + var env = try std.process.getEnvMap(alloc); + defer env.deinit(); + var app: RotInt = .{ .allocator = alloc, + .conf = Config.load(alloc, &env) catch cfg: { + log.warn("Could not load config file. Using defaults.", .{}); + break :cfg Config.default(); + }, .termbuffer = tty.bufferedWriter(), .vx = &vx, .loop = &loop,