diff --git a/.woodpecker.yml b/.woodpecker.yml index 4db927b..bb30a5d 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,19 +9,23 @@ clone: recursive: false pipeline: lint: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - ./tools/fmt-check.sh test: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - zig build test sdl2: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - zig build -Ddriver=sdl2 aarch64: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - - zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Drelease-safe -Dstrip + - zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe -Dstrip - sha256sum zig-out/bin/nd zig-out/bin/ngui + playground: + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 + commands: + - zig build guiplay btcrpc diff --git a/README.md b/README.md index e3e1d95..8cc86b8 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ build for rpi: - zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Drelease-safe -Dstrip + zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Doptimize=ReleaseSafe -Dstrip otherwise just `zig build` on dev host ## local development -you'll need [zig v0.10.x](https://ziglang.org/download/). +you'll need [zig v0.11.x](https://ziglang.org/download/). if working on the gui, also [SDL2](https://www.libsdl.org/). note that compiling the daemon on macOS is currently unsupported since @@ -58,17 +58,17 @@ to make a new image and switch the CI to use it, first modify the [ci-containerfile](tools/ci-containerfile) and produce the image locally: podman build --rm -t ndg-ci -f ./tools/ci-containerfile \ - --build-arg ZIGURL=https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz + --build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz then tag it with the target URL, for example: - podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.10.1:v1 + podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.11.0:v2 generate an [access token](https://git.qcode.ch/user/settings/applications), login to the container registry and push the image to remote: podman login git.qcode.ch - podman push git.qcode.ch/nakamochi/ci-zig0.10.1:v1 + podman push git.qcode.ch/nakamochi/ci-zig0.11.0:v2 the image will be available at https://git.qcode.ch/nakamochi/-/packages/ diff --git a/build.zig b/build.zig index f1eb2d2..7cc13ca 100644 --- a/build.zig +++ b/build.zig @@ -1,20 +1,26 @@ const std = @import("std"); -const nifbuild = @import("lib/nif/build.zig"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); - + const optimize = b.standardOptimizeOption(.{}); const strip = b.option(bool, "strip", "strip output binary; default: false") orelse false; const drv = b.option(DriverTarget, "driver", "display and input drivers combo; default: sdl2") orelse .sdl2; const disp_horiz = b.option(u32, "horiz", "display horizontal pixels count; default: 800") orelse 800; const disp_vert = b.option(u32, "vert", "display vertical pixels count; default: 480") orelse 480; - const lvgl_loglevel = b.option(LVGLLogLevel, "lvgl_loglevel", "LVGL lib logging level") orelse LVGLLogLevel.default(mode); + const lvgl_loglevel = b.option(LVGLLogLevel, "lvgl_loglevel", "LVGL lib logging level") orelse LVGLLogLevel.default(optimize); const inver = b.option([]const u8, "version", "semantic version of the build; must match git tag when available"); const buildopts = b.addOptions(); buildopts.addOption(DriverTarget, "driver", drv); const semver_step = VersionStep.create(b, buildopts, inver); + buildopts.step.dependOn(semver_step); + + // network interface (nif) standalone library used by the daemon and tests. + const libnif_dep = b.anonymousDependency("lib/nif", @import("lib/nif/build.zig"), .{ + .target = target, + .optimize = optimize, + }); + const libnif = libnif_dep.artifact("nif"); const common_cflags = .{ "-Wall", @@ -25,17 +31,18 @@ pub fn build(b: *std.build.Builder) void { }; // gui build - const ngui = b.addExecutable("ngui", "src/ngui.zig"); - ngui.setTarget(target); - ngui.setBuildMode(mode); + const ngui = b.addExecutable(.{ + .name = "ngui", + .root_source_file = .{ .path = "src/ngui.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + }); ngui.pie = true; ngui.strip = strip; - ngui.step.dependOn(semver_step); - - ngui.addPackage(buildopts.getPackage("build_options")); - ngui.addIncludePath("lib"); - ngui.addIncludePath("src/ui/c"); - ngui.linkLibC(); + ngui.addOptions("build_options", buildopts); + ngui.addIncludePath(.{ .path = "lib" }); + ngui.addIncludePath(.{ .path = "src/ui/c" }); const lvgl_flags = .{ "-std=c11", @@ -68,14 +75,14 @@ pub fn build(b: *std.build.Builder) void { switch (drv) { .sdl2 => { ngui.addCSourceFiles(lvgl_sdl2_src, &lvgl_flags); - ngui.addCSourceFile("src/ui/c/drv_sdl2.c", &ngui_cflags); + ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_sdl2.c" }, .flags = &ngui_cflags }); ngui.defineCMacro("NM_DRV_SDL2", null); ngui.defineCMacro("USE_SDL", null); ngui.linkSystemLibrary("SDL2"); }, .fbev => { ngui.addCSourceFiles(lvgl_fbev_src, &lvgl_flags); - ngui.addCSourceFile("src/ui/c/drv_fbev.c", &ngui_cflags); + ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_fbev.c" }, .flags = &ngui_cflags }); ngui.defineCMacro("NM_DRV_FBEV", null); ngui.defineCMacro("USE_FBDEV", null); ngui.defineCMacro("USE_EVDEV", null); @@ -83,68 +90,77 @@ pub fn build(b: *std.build.Builder) void { } const ngui_build_step = b.step("ngui", "build ngui (nakamochi gui)"); - ngui_build_step.dependOn(&b.addInstallArtifact(ngui).step); + ngui_build_step.dependOn(&b.addInstallArtifact(ngui, .{}).step); // daemon build - const nd = b.addExecutable("nd", "src/nd.zig"); - nd.setTarget(target); - nd.setBuildMode(mode); + const nd = b.addExecutable(.{ + .name = "nd", + .root_source_file = .{ .path = "src/nd.zig" }, + .target = target, + .optimize = optimize, + }); nd.pie = true; nd.strip = strip; - nd.step.dependOn(semver_step); - - nd.addPackage(buildopts.getPackage("build_options")); - nifbuild.addPkg(b, nd, "lib/nif"); - const niflib = nifbuild.library(b, "lib/nif"); - niflib.setTarget(target); - niflib.setBuildMode(mode); - nd.linkLibrary(niflib); + nd.addOptions("build_options", buildopts); + nd.addModule("nif", libnif_dep.module("nif")); + nd.linkLibrary(libnif); const nd_build_step = b.step("nd", "build nd (nakamochi daemon)"); - nd_build_step.dependOn(&b.addInstallArtifact(nd).step); - - // default build - const build_all_step = b.step("all", "build nd and ngui"); - build_all_step.dependOn(ngui_build_step); - build_all_step.dependOn(nd_build_step); - b.default_step.dependOn(build_all_step); + nd_build_step.dependOn(&b.addInstallArtifact(nd, .{}).step); + // automated tests { - const tests = b.addTest("src/test.zig"); - tests.setTarget(target); - tests.setBuildMode(mode); - tests.linkLibC(); - tests.addPackage(buildopts.getPackage("build_options")); - nifbuild.addPkg(b, tests, "lib/nif"); - - const f = b.option([]const u8, "test-filter", "run tests matching the filter"); - tests.setFilter(f); - + const tests = b.addTest(.{ + .root_source_file = .{ .path = "src/test.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + .filter = b.option([]const u8, "test-filter", "run tests matching the filter"), + }); + tests.addOptions("build_options", buildopts); + tests.addModule("nif", libnif_dep.module("nif")); + tests.linkLibrary(libnif); + + const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "run tests"); - test_step.dependOn(&tests.step); + test_step.dependOn(&run_tests.step); } + // GUI playground { - const guiplay = b.addExecutable("guiplay", "src/test/guiplay.zig"); - guiplay.setTarget(target); - guiplay.setBuildMode(mode); - guiplay.step.dependOn(semver_step); - guiplay.addPackagePath("comm", "src/comm.zig"); + const guiplay = b.addExecutable(.{ + .name = "guiplay", + .root_source_file = .{ .path = "src/test/guiplay.zig" }, + .target = target, + .optimize = optimize, + }); + guiplay.addModule("comm", b.createModule(.{ .source_file = .{ .path = "src/comm.zig" } })); const guiplay_build_step = b.step("guiplay", "build GUI playground"); - guiplay_build_step.dependOn(&b.addInstallArtifact(guiplay).step); + guiplay_build_step.dependOn(&b.addInstallArtifact(guiplay, .{}).step); guiplay_build_step.dependOn(ngui_build_step); } + // bitcoind RPC client playground { - const btcrpc = b.addExecutable("btcrpc", "src/test/btcrpc.zig"); - btcrpc.setTarget(target); - btcrpc.setBuildMode(mode); + const btcrpc = b.addExecutable(.{ + .name = "btcrpc", + .root_source_file = .{ .path = "src/test/btcrpc.zig" }, + .target = target, + .optimize = optimize, + }); btcrpc.strip = strip; - btcrpc.addPackagePath("bitcoindrpc", "src/nd/bitcoindrpc.zig"); + btcrpc.addModule("bitcoindrpc", b.createModule(.{ .source_file = .{ .path = "src/nd/bitcoindrpc.zig" } })); + const btcrpc_build_step = b.step("btcrpc", "bitcoind RPC client playground"); - btcrpc_build_step.dependOn(&b.addInstallArtifact(btcrpc).step); + btcrpc_build_step.dependOn(&b.addInstallArtifact(btcrpc, .{}).step); } + + // default build step + const build_all_step = b.step("all", "build nd and ngui (default step)"); + build_all_step.dependOn(ngui_build_step); + build_all_step.dependOn(nd_build_step); + b.default_step.dependOn(build_all_step); } const DriverTarget = enum { @@ -354,23 +370,28 @@ const LVGLLogLevel = enum { /// git tag is found. const VersionStep = struct { inver: ?[]const u8, // input version in std.SemanticVersion.parse format - buildopts: *std.build.OptionsStep, // where to store the build version + buildopts: *std.Build.Step.Options, // where to store the build version - b: *std.build.Builder, - step: std.build.Step, + b: *std.Build, + step: std.Build.Step, - fn create(b: *std.build.Builder, o: *std.build.OptionsStep, inver: ?[]const u8) *std.build.Step { + fn create(b: *std.Build, o: *std.Build.Step.Options, inver: ?[]const u8) *std.Build.Step { const vstep = b.allocator.create(VersionStep) catch unreachable; vstep.* = VersionStep{ .inver = inver, .buildopts = o, .b = b, - .step = std.build.Step.init(.custom, "VersionStep: ndg semver", b.allocator, make), + .step = std.Build.Step.init(.{ + .id = .custom, + .name = "VersionStep: ndg semver", + .owner = b, + .makeFn = make, + }), }; return &vstep.step; } - fn make(step: *std.build.Step) anyerror!void { + fn make(step: *std.Build.Step, _: *std.Progress.Node) anyerror!void { const self = @fieldParentPtr(VersionStep, "step", step); const semver = try self.eval(); std.log.info("build version: {any}", .{semver}); diff --git a/lib/nif/build.zig b/lib/nif/build.zig index dfb9207..6a32113 100644 --- a/lib/nif/build.zig +++ b/lib/nif/build.zig @@ -1,21 +1,23 @@ -const build = @import("std").build; +const std = @import("std"); -pub fn addPkg(b: *build.Builder, obj: *build.LibExeObjStep, prefix: []const u8) void { - obj.addPackagePath("nif", pkgPath(b, prefix)); -} - -pub fn pkgPath(b: *build.Builder, prefix: []const u8) []const u8 { - return b.pathJoin(&.{ prefix, "nif.zig" }); -} +pub fn build(b: *std.Build) void { + _ = b.addModule("nif", .{ .source_file = .{ .path = "nif.zig" } }); -pub fn library(b: *build.Builder, prefix: []const u8) *build.LibExeObjStep { - const lib = b.addStaticLibrary("nif", b.pathJoin(&.{ prefix, "nif.zig" })); - lib.addIncludePath(b.pathJoin(&.{ prefix, "wpa_supplicant" })); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const lib = b.addStaticLibrary(.{ + .name = "nif", + .root_source_file = .{ .path = "nif.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + }); lib.defineCMacro("CONFIG_CTRL_IFACE", null); lib.defineCMacro("CONFIG_CTRL_IFACE_UNIX", null); + lib.addIncludePath(.{ .path = "wpa_supplicant" }); lib.addCSourceFiles(&.{ - b.pathJoin(&.{ prefix, "wpa_supplicant/wpa_ctrl.c" }), - b.pathJoin(&.{ prefix, "wpa_supplicant/os_unix.c" }), + "wpa_supplicant/wpa_ctrl.c", + "wpa_supplicant/os_unix.c", }, &.{ "-Wall", "-Wextra", @@ -24,6 +26,5 @@ pub fn library(b: *build.Builder, prefix: []const u8) *build.LibExeObjStep { "-Wunused-parameter", "-Werror", }); - lib.linkLibC(); - return lib; + b.installArtifact(lib); } diff --git a/lib/nif/nif.zig b/lib/nif/nif.zig index 7a43961..e9c6556 100644 --- a/lib/nif/nif.zig +++ b/lib/nif/nif.zig @@ -46,7 +46,7 @@ pub fn pubAddresses(allocator: mem.Allocator, ifname: ?[]const u8) ![]net.Addres // skip loopbacks and those which are not "up" continue; } - const ipaddr = net.Address.initPosix(@alignCast(4, sa)); // initPosix makes a copy + const ipaddr = net.Address.initPosix(@alignCast(sa)); // initPosix makes a copy if (ipaddr.any.family == os.AF.INET6 and ipaddr.in6.sa.scope_id > 0) { // want only global, with 0 scope // non-zero scopes make sense for link-local addr only. diff --git a/src/comm.zig b/src/comm.zig index dd2db44..2b5c811 100644 --- a/src/comm.zig +++ b/src/comm.zig @@ -103,22 +103,37 @@ pub const MessageTag = enum(u16) { // next: 0x0b }; +/// the return value type from `read` fn. +pub const ParsedMessage = struct { + value: Message, + arena: ?*std.heap.ArenaAllocator = null, // null for void message tags + + /// releases all resources used by the message. + pub fn deinit(self: @This()) void { + if (self.arena) |a| { + const allocator = a.child_allocator; + a.deinit(); + allocator.destroy(a); + } + } +}; + /// reads and parses a single message from the input stream reader. /// propagates reader errors as is. for example, a closed reader returns /// error.EndOfStream. /// -/// callers must deallocate resources with Message.free when done. -pub fn read(allocator: mem.Allocator, reader: anytype) !Message { +/// callers must deallocate resources with ParsedMessage.deinit when done. +pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage { // alternative is @intToEnum(reader.ReadIntLittle(u16)) but it may panic. const tag = try reader.readEnum(MessageTag, .Little); const len = try reader.readIntLittle(u64); if (len == 0) { return switch (tag) { - .ping => Message{ .ping = {} }, - .pong => Message{ .pong = {} }, - .poweroff => Message{ .poweroff = {} }, - .standby => Message{ .standby = {} }, - .wakeup => Message{ .wakeup = {} }, + .ping => .{ .value = .{ .ping = {} } }, + .pong => .{ .value = .{ .pong = {} } }, + .poweroff => .{ .value = .{ .poweroff = {} } }, + .standby => .{ .value = .{ .standby = {} } }, + .wakeup => .{ .value = .{ .wakeup = {} } }, else => Error.CommReadZeroLenInNonVoidTag, }; } @@ -126,24 +141,22 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !Message { var bytes = try allocator.alloc(u8, len); defer allocator.free(bytes); try reader.readNoEof(bytes); - const jopt = json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; - var jstream = json.TokenStream.init(bytes); return switch (tag) { .ping, .pong, .poweroff, .standby, .wakeup => unreachable, // handled above - .wifi_connect => Message{ - .wifi_connect = try json.parse(Message.WifiConnect, &jstream, jopt), - }, - .network_report => Message{ - .network_report = try json.parse(Message.NetworkReport, &jstream, jopt), - }, - .get_network_report => Message{ - .get_network_report = try json.parse(Message.GetNetworkReport, &jstream, jopt), - }, - .poweroff_progress => Message{ - .poweroff_progress = try json.parse(Message.PoweroffProgress, &jstream, jopt), - }, - .bitcoind_report => Message{ - .bitcoind_report = try json.parse(Message.BitcoindReport, &jstream, jopt), + inline else => |t| { + var arena = try allocator.create(std.heap.ArenaAllocator); + arena.* = std.heap.ArenaAllocator.init(allocator); + errdefer { + arena.deinit(); + allocator.destroy(arena); + } + const jopt = std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = .alloc_always }; + const v = try json.parseFromSliceLeaky(std.meta.TagPayload(Message, t), arena.allocator(), bytes, jopt); + const parsed = ParsedMessage{ + .arena = arena, + .value = @unionInit(Message, @tagName(t), v), + }; + return parsed; }, }; } @@ -151,35 +164,25 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !Message { /// outputs the message msg using writer. /// all allocated resources are freed upon return. pub fn write(allocator: mem.Allocator, writer: anytype, msg: Message) !void { - const jopt = .{ .whitespace = null }; var data = ByteArrayList.init(allocator); defer data.deinit(); switch (msg) { .ping, .pong, .poweroff, .standby, .wakeup => {}, // zero length payload - .wifi_connect => try json.stringify(msg.wifi_connect, jopt, data.writer()), - .network_report => try json.stringify(msg.network_report, jopt, data.writer()), - .get_network_report => try json.stringify(msg.get_network_report, jopt, data.writer()), - .poweroff_progress => try json.stringify(msg.poweroff_progress, jopt, data.writer()), - .bitcoind_report => try json.stringify(msg.bitcoind_report, jopt, data.writer()), + .wifi_connect => try json.stringify(msg.wifi_connect, .{}, data.writer()), + .network_report => try json.stringify(msg.network_report, .{}, data.writer()), + .get_network_report => try json.stringify(msg.get_network_report, .{}, data.writer()), + .poweroff_progress => try json.stringify(msg.poweroff_progress, .{}, data.writer()), + .bitcoind_report => try json.stringify(msg.bitcoind_report, .{}, data.writer()), } if (data.items.len > std.math.maxInt(u64)) { return Error.CommWriteTooLarge; } - try writer.writeIntLittle(u16, @enumToInt(msg)); + try writer.writeIntLittle(u16, @intFromEnum(msg)); try writer.writeIntLittle(u64, data.items.len); try writer.writeAll(data.items); } -pub fn free(allocator: mem.Allocator, m: Message) void { - switch (m) { - .ping, .pong, .poweroff, .standby, .wakeup => {}, // zero length payload - else => |v| { - json.parseFree(@TypeOf(v), v, .{ .allocator = allocator }); - }, - } -} - // TODO: use fifo // // var buf = std.fifo.LinearFifo(u8, .Dynamic).init(t.allocator); @@ -206,16 +209,16 @@ test "read" { var buf = std.ArrayList(u8).init(t.allocator); defer buf.deinit(); - try buf.writer().writeIntLittle(u16, @enumToInt(msg)); + try buf.writer().writeIntLittle(u16, @intFromEnum(msg)); try buf.writer().writeIntLittle(u64, data.items.len); try buf.writer().writeAll(data.items); var bs = std.io.fixedBufferStream(buf.items); const res = try read(t.allocator, bs.reader()); - defer free(t.allocator, res); + defer res.deinit(); - try t.expectEqualStrings(msg.wifi_connect.ssid, res.wifi_connect.ssid); - try t.expectEqualStrings(msg.wifi_connect.password, res.wifi_connect.password); + try t.expectEqualStrings(msg.wifi_connect.ssid, res.value.wifi_connect.ssid); + try t.expectEqualStrings(msg.wifi_connect.password, res.value.wifi_connect.password); } test "write" { @@ -229,7 +232,7 @@ test "write" { const payload = "{\"ssid\":\"wlan\",\"password\":\"secret\"}"; var js = std.ArrayList(u8).init(t.allocator); defer js.deinit(); - try js.writer().writeIntLittle(u16, @enumToInt(msg)); + try js.writer().writeIntLittle(u16, @intFromEnum(msg)); try js.writer().writeIntLittle(u64, payload.len); try js.appendSlice(payload); @@ -255,8 +258,8 @@ test "write/read void tags" { try write(t.allocator, buf.writer(), m); var bs = std.io.fixedBufferStream(buf.items); const res = try read(t.allocator, bs.reader()); - free(t.allocator, res); // noop - try t.expectEqual(m, res); + res.deinit(); // noop due to void type + try t.expectEqual(m, res.value); } } @@ -283,7 +286,7 @@ test "msg sequence" { var bs = std.io.fixedBufferStream(buf.items); for (msgs) |m| { const res = try read(t.allocator, bs.reader()); - defer free(t.allocator, res); - try t.expectEqual(@as(MessageTag, m), @as(MessageTag, res)); + defer res.deinit(); + try t.expectEqual(@as(MessageTag, m), @as(MessageTag, res.value)); } } diff --git a/src/nd.zig b/src/nd.zig index a1b1530..ab7f467 100644 --- a/src/nd.zig +++ b/src/nd.zig @@ -126,7 +126,7 @@ fn sighandler(sig: c_int) callconv(.C) void { pub fn main() !void { // main heap allocator used throughout the lifetime of nd var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/nd/Daemon.zig b/src/nd/Daemon.zig index 6abdfc1..fc31daf 100644 --- a/src/nd/Daemon.zig +++ b/src/nd/Daemon.zig @@ -82,7 +82,7 @@ pub fn init(a: std.mem.Allocator, r: std.fs.File.Reader, w: std.fs.File.Writer, .uiwriter = w, .wpa_ctrl = try types.WpaControl.open(wpa), .state = .stopped, - .services = svlist.toOwnedSlice(), + .services = try svlist.toOwnedSlice(), // send a network report right at start without wifi scan to make it faster. .want_network_report = true, .want_wifi_scan = false, @@ -297,7 +297,7 @@ fn commThreadLoop(self: *Daemon) void { std.atomic.spinLoopHint(); time.sleep(100 * time.ns_per_ms); - const msg = comm.read(self.allocator, self.uireader) catch |err| { + const res = comm.read(self.allocator, self.uireader) catch |err| { self.mu.lock(); defer self.mu.unlock(); if (self.want_stop) { @@ -316,7 +316,9 @@ fn commThreadLoop(self: *Daemon) void { }, } }; + defer res.deinit(); + const msg = res.value; logger.debug("got msg: {s}", .{@tagName(msg)}); switch (msg) { .pong => { @@ -343,12 +345,12 @@ fn commThreadLoop(self: *Daemon) void { }, else => logger.warn("unhandled msg tag {s}", .{@tagName(msg)}), } - comm.free(self.allocator, msg); self.mu.lock(); quit = self.want_stop; self.mu.unlock(); } + logger.info("exiting comm thread loop", .{}); } @@ -356,8 +358,8 @@ fn commThreadLoop(self: *Daemon) void { fn sendPoweroffReport(self: *Daemon) !void { var svstat = try self.allocator.alloc(comm.Message.PoweroffProgress.Service, self.services.len); defer self.allocator.free(svstat); - for (self.services) |*sv, i| { - svstat[i] = .{ + for (self.services, svstat) |*sv, *stat| { + stat.* = .{ .name = sv.name, .stopped = sv.status() == .stopped, .err = if (sv.lastStopError()) |err| @errorName(err) else null, @@ -616,19 +618,19 @@ test "start-poweroff" { try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = false, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null }, - } } }, msg1); + } } }, msg1.value); const msg2 = try comm.read(arena, gui_reader); try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null }, - } } }, msg2); + } } }, msg2.value); const msg3 = try comm.read(arena, gui_reader); try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "bitcoind", .stopped = true, .err = null }, - } } }, msg3); + } } }, msg3.value); // TODO: ensure "poweroff" was executed once custom runner is in a zig release; // need custom runner to set up a global registry for child processes. diff --git a/src/nd/bitcoindrpc.zig b/src/nd/bitcoindrpc.zig index 7f0ce5f..670a28e 100644 --- a/src/nd/bitcoindrpc.zig +++ b/src/nd/bitcoindrpc.zig @@ -55,16 +55,7 @@ pub const Client = struct { }; pub fn Result(comptime m: Method) type { - return struct { - value: ResultValue(m), - arena: *ArenaAllocator, - - pub fn deinit(self: @This()) void { - const allocator = self.arena.child_allocator; - self.arena.deinit(); - allocator.destroy(self.arena); - } - }; + return std.json.Parsed(ResultValue(m)); } pub fn ResultValue(comptime m: Method) type { @@ -142,27 +133,16 @@ pub const Client = struct { } fn parseResponse(self: Client, comptime m: Method, b: []const u8) !Result(m) { - var result = Result(m){ - .value = undefined, - .arena = try self.allocator.create(ArenaAllocator), - }; - errdefer self.allocator.destroy(result.arena); - result.arena.* = ArenaAllocator.init(self.allocator); - - var jstream = std.json.TokenStream.init(b); - const jopt = std.json.ParseOptions{ .allocator = result.arena.allocator(), .ignore_unknown_fields = true }; - const resp = try std.json.parse(RpcResponse(m), &jstream, jopt); - - errdefer result.arena.deinit(); - if (resp.@"error") |errfield| { + const jopt = std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = .alloc_always }; + const resp = try std.json.parseFromSlice(RpcResponse(m), self.allocator, b, jopt); + errdefer resp.deinit(); + if (resp.value.@"error") |errfield| { return rpcErrorFromCode(errfield.code) orelse error.UnknownError; } - if (resp.result == null) { + if (resp.value.result == null) { return error.NullResult; } - - result.value = resp.result.?; - return result; + return .{ .value = resp.value.result.?, .arena = resp.arena }; } fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 { @@ -189,7 +169,7 @@ pub const Client = struct { try w.print("Content-Length: {d}\r\n", .{jreq.items.len}); try w.writeAll("\r\n"); try w.writeAll(jreq.items); - return bytes.toOwnedSlice(); + return try bytes.toOwnedSlice(); } fn getAuthBase64(self: Client) ![]const u8 { diff --git a/src/nd/network.zig b/src/nd/network.zig index cfe5e54..610d315 100644 --- a/src/nd/network.zig +++ b/src/nd/network.zig @@ -62,40 +62,34 @@ pub fn addWifi(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, ssid: []const u8 /// reports network status to the writer w in `comm.Message.NetworkReport` format. pub fn sendReport(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, w: anytype) !void { + var arena_state = std.heap.ArenaAllocator.init(gpa); + defer arena_state.deinit(); + const arena = arena_state.allocator(); var report = comm.Message.NetworkReport{ .ipaddrs = undefined, .wifi_ssid = null, - .wifi_scan_networks = undefined, + .wifi_scan_networks = &.{}, }; // fetch all public IP addresses using getifaddrs - const pubaddr = try nif.pubAddresses(gpa, null); - defer gpa.free(pubaddr); - //var addrs = std.ArrayList([]).init(t.allocator); - var ipaddrs = try gpa.alloc([]const u8, pubaddr.len); - for (pubaddr) |a, i| { - ipaddrs[i] = try std.fmt.allocPrint(gpa, "{s}", .{a}); + const pubaddr = try nif.pubAddresses(arena, null); + var ipaddr = try std.ArrayList([]const u8).initCapacity(arena, pubaddr.len); + for (pubaddr) |apub| { + try ipaddr.append(try std.fmt.allocPrint(arena, "{}", .{apub})); } - defer { - for (ipaddrs) |a| gpa.free(a); - gpa.free(ipaddrs); - } - report.ipaddrs = ipaddrs; + report.ipaddrs = try ipaddr.toOwnedSlice(); // get currently connected SSID, if any, from WPA ctrl - const ssid = queryWifiSSID(gpa, wpa_ctrl) catch |err| blk: { + report.wifi_ssid = queryWifiSSID(arena, wpa_ctrl) catch |err| blk: { logger.err("queryWifiSsid: {any}", .{err}); break :blk null; }; - defer if (ssid) |v| gpa.free(v); - report.wifi_ssid = ssid; // fetch available wifi networks from scan results using WPA ctrl - var wifi_networks: ?types.StringList = if (queryWifiScanResults(gpa, wpa_ctrl)) |v| v else |err| blk: { + var wifi_networks: ?types.StringList = if (queryWifiScanResults(arena, wpa_ctrl)) |v| v else |err| blk: { logger.err("queryWifiScanResults: {any}", .{err}); break :blk null; }; - defer if (wifi_networks) |*list| list.deinit(); if (wifi_networks) |list| { report.wifi_scan_networks = list.items(); } @@ -179,5 +173,5 @@ fn queryWifiNetworksList(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, filter } list.append(id) catch {}; // grab anything we can } - return list.toOwnedSlice(); + return try list.toOwnedSlice(); } diff --git a/src/ngui.zig b/src/ngui.zig index fe771da..0055b14 100644 --- a/src/ngui.zig +++ b/src/ngui.zig @@ -10,12 +10,6 @@ const lvgl = @import("ui/lvgl.zig"); const screen = @import("ui/screen.zig"); const symbol = @import("ui/symbol.zig"); -/// SIGPIPE is triggered when a process attempts to write to a broken pipe. -/// by default, SIGPIPE terminates the process without invoking a panic handler. -/// this declaration makes such writes result in EPIPE (error.BrokenPipe) to let -/// the program can handle it. -pub const keep_sigpipe = true; - const logger = std.log.scoped(.ngui); // these are auto-closed as soon as main fn terminates. @@ -46,39 +40,40 @@ var state: enum { /// while deinit and replace handle concurrency, field access requires holding mu. var last_report: struct { mu: std.Thread.Mutex = .{}, - network: ?comm.Message.NetworkReport = null, - bitcoind: ?comm.Message.BitcoindReport = null, + network: ?comm.ParsedMessage = null, // NetworkReport + bitcoind: ?comm.ParsedMessage = null, // BitcoinReport fn deinit(self: *@This()) void { self.mu.lock(); defer self.mu.unlock(); if (self.network) |v| { - comm.free(gpa, .{ .network_report = v }); + v.deinit(); self.network = null; } if (self.bitcoind) |v| { - comm.free(gpa, .{ .bitcoind_report = v }); + v.deinit(); self.bitcoind = null; } } - fn replace(self: *@This(), new: anytype) void { + fn replace(self: *@This(), new: comm.ParsedMessage) void { self.mu.lock(); defer self.mu.unlock(); - switch (@TypeOf(new)) { - comm.Message.NetworkReport => { + const tag: comm.MessageTag = new.value; + switch (tag) { + .network_report => { if (self.network) |old| { - comm.free(gpa, .{ .network_report = old }); + old.deinit(); } self.network = new; }, - comm.Message.BitcoindReport => { + .bitcoind_report => { if (self.bitcoind) |old| { - comm.free(gpa, .{ .bitcoind_report = old }); + old.deinit(); } self.bitcoind = new; }, - else => @compileError("unhandled type: " ++ @typeName(@TypeOf(new))), + else => |t| logger.err("last_report: replace: unhandled tag {}", .{t}), } } } = .{}; @@ -103,9 +98,9 @@ export fn nm_get_curr_tick() u32 { const ms = tick_timer.read() / time.ns_per_ms; const over = ms >> 32; if (over > 0) { - return @truncate(u32, over); // LVGL deals with overflow correctly + return @truncate(over); // LVGL deals with overflow correctly } - return @truncate(u32, ms); + return @truncate(ms); } export fn nm_check_idle_time(_: *lvgl.LvTimer) void { @@ -223,27 +218,27 @@ fn commThreadLoopCycle() !void { ui_mutex.lock(); // guards the state and all UI calls below defer ui_mutex.unlock(); switch (state) { - .standby => switch (msg) { + .standby => switch (msg.value) { .ping => try comm.write(gpa, stdout, comm.Message.pong), - .network_report => |v| last_report.replace(v), - .bitcoind_report => |v| last_report.replace(v), - else => logger.debug("ignoring {s}: in standby", .{@tagName(msg)}), + .network_report => last_report.replace(msg), + .bitcoind_report => last_report.replace(msg), + else => logger.debug("ignoring {s}: in standby", .{@tagName(msg.value)}), }, - .active, .alert => switch (msg) { + .active, .alert => switch (msg.value) { .ping => try comm.write(gpa, stdout, comm.Message.pong), .poweroff_progress => |rep| { ui.poweroff.updateStatus(rep) catch |err| logger.err("poweroff.updateStatus: {any}", .{err}); - comm.free(gpa, msg); + msg.deinit(); }, .network_report => |rep| { updateNetworkStatus(rep) catch |err| logger.err("updateNetworkStatus: {any}", .{err}); - last_report.replace(rep); + last_report.replace(msg); }, .bitcoind_report => |rep| { ui.bitcoin.updateTabPanel(rep) catch |err| logger.err("bitcoin.updateTabPanel: {any}", .{err}); - last_report.replace(rep); + last_report.replace(msg); }, - else => logger.warn("unhandled msg tag {s}", .{@tagName(msg)}), + else => logger.warn("unhandled msg tag {s}", .{@tagName(msg.value)}), }, } } @@ -281,11 +276,15 @@ fn uiThreadLoop() void { last_report.mu.lock(); defer last_report.mu.unlock(); - if (last_report.network) |rep| { - updateNetworkStatus(rep) catch |err| logger.err("updateNetworkStatus: {any}", .{err}); + if (last_report.network) |msg| { + updateNetworkStatus(msg.value.network_report) catch |err| { + logger.err("updateNetworkStatus: {any}", .{err}); + }; } - if (last_report.bitcoind) |rep| { - ui.bitcoin.updateTabPanel(rep) catch |err| logger.err("bitcoin.updateTabPanel: {any}", .{err}); + if (last_report.bitcoind) |msg| { + ui.bitcoin.updateTabPanel(msg.value.bitcoind_report) catch |err| { + logger.err("bitcoin.updateTabPanel: {any}", .{err}); + }; } } continue; @@ -342,7 +341,7 @@ fn sighandler(sig: c_int) callconv(.C) void { pub fn main() anyerror!void { // main heap allocator used through the lifetime of nd var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; gpa = gpa_state.allocator(); diff --git a/src/test.zig b/src/test.zig index 0e2e995..b5744c5 100644 --- a/src/test.zig +++ b/src/test.zig @@ -55,8 +55,8 @@ pub const TestChildProcess = struct { pub fn init(argv: []const []const u8, allocator: std.mem.Allocator) TestChildProcess { var adup = allocator.alloc([]u8, argv.len) catch unreachable; - for (argv) |v, i| { - adup[i] = allocator.dupe(u8, v) catch unreachable; + for (argv, adup) |v, *dup| { + dup.* = allocator.dupe(u8, v) catch unreachable; } return .{ .allocator = allocator, @@ -167,10 +167,10 @@ pub const TestWpaControl = struct { } }; -/// similar to std.testing.expectEqual but compares slices with expectEqualSlices +/// similar to std.testing.expectEqualDeep but compares slices with expectEqualSlices /// or expectEqualStrings where slice element is a u8. +/// unhandled types are passed to std.testing.expectEqualDeep. pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { - const t = std.testing; switch (@typeInfo(@TypeOf(actual))) { .Pointer => |p| { switch (p.size) { @@ -185,7 +185,7 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { } break :blk null; }; - const n = std.math.min(expected.len, actual.len); + const n = @min(expected.len, actual.len); var i: usize = 0; while (i < n) : (i += 1) { expectDeepEqual(expected[i], actual[i]) catch |e| { @@ -199,14 +199,14 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { }, else => { if (p.child == u8) { - try t.expectEqualStrings(expected, actual); + try std.testing.expectEqualStrings(expected, actual); } else { - try t.expectEqualSlices(p.child, expected, actual); + try std.testing.expectEqualSlices(p.child, expected, actual); } }, } }, - else => try t.expectEqual(expected, actual), + else => try std.testing.expectEqualDeep(expected, actual), } }, .Struct => |st| { @@ -238,17 +238,15 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { } const Tag = std.meta.Tag(@TypeOf(expected)); const atag = @as(Tag, actual); - try t.expectEqual(@as(Tag, expected), atag); - inline for (u.fields) |f| { - if (std.mem.eql(u8, f.name, @tagName(atag))) { - try expectDeepEqual(@field(expected, f.name), @field(actual, f.name)); - return; - } + try std.testing.expectEqual(@as(Tag, expected), atag); + switch (expected) { + inline else => |x, tag| { + try expectDeepEqual(x, @field(actual, @tagName(tag))); + }, } - unreachable; }, else => { - try t.expectEqual(expected, actual); + try std.testing.expectEqualDeep(expected, actual); }, } } diff --git a/src/test/btcrpc.zig b/src/test/btcrpc.zig index 58c300f..5dff002 100644 --- a/src/test/btcrpc.zig +++ b/src/test/btcrpc.zig @@ -4,7 +4,7 @@ const bitcoinrpc = @import("bitcoindrpc"); pub fn main() !void { var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { std.debug.print("memory leaks detected!", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/test/guiplay.zig b/src/test/guiplay.zig index 3063b97..29e3999 100644 --- a/src/test/guiplay.zig +++ b/src/test/guiplay.zig @@ -87,9 +87,10 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void { logger.err("comm.read: {any}", .{err}); continue; }; + defer msg.deinit(); - logger.debug("got ui msg tagged {s}", .{@tagName(msg)}); - switch (msg) { + logger.debug("got msg: {s}", .{@tagName(msg.value)}); + switch (msg.value) { .pong => { logger.info("received pong from ngui", .{}); }, @@ -131,18 +132,18 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { while (true) { time.sleep(time.ns_per_s); - if (sectimer.read() < time.ns_per_s) { + if (sectimer.read() < 3 * time.ns_per_s) { continue; } - sectimer.reset(); + block_count += 1; const now = time.timestamp(); const btcrep: comm.Message.BitcoindReport = .{ .blocks = block_count, .headers = block_count, - .timestamp = @intCast(u64, now), + .timestamp = @intCast(now), .hash = "00000000000000000002bf8029f6be4e40b4a3e0e161b6a1044ddaf9eb126504", .ibd = false, .verifyprogress = 100, @@ -155,7 +156,7 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { .mempool = .{ .loaded = true, .txcount = 100000 + block_count, - .usage = std.math.min(200123456 + block_count * 10, 300000000), + .usage = @min(200123456 + block_count * 10, 300000000), .max = 300000000, .totalfee = 2.23049932, .minfee = 0.00004155, @@ -168,7 +169,7 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { pub fn main() !void { var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/ui/bitcoin.zig b/src/ui/bitcoin.zig index dab529e..3ae53d5 100644 --- a/src/ui/bitcoin.zig +++ b/src/ui/bitcoin.zig @@ -106,10 +106,10 @@ pub fn updateTabPanel(rep: comm.Message.BitcoindReport) !void { if (rep.mempool.max == 0) { break :pct 0; } - const v = @intToFloat(f64, rep.mempool.usage) / @intToFloat(f64, rep.mempool.max); - break :pct @floatCast(f32, v * 100); + const v = @as(f64, @floatFromInt(rep.mempool.usage)) / @as(f64, @floatFromInt(rep.mempool.max)); + break :pct @floatCast(v * 100); }; - tab.mempool.usage_bar.setValue(@floatToInt(i32, @round(mempool_pct))); + tab.mempool.usage_bar.setValue(@as(i32, @intFromFloat(@round(mempool_pct)))); try tab.mempool.usage_lab.setTextFmt(&buf, "{:.1} " ++ cmark ++ "out of# {:.1} ({d:.1}%)", .{ fmt.fmtIntSizeBin(rep.mempool.usage), fmt.fmtIntSizeBin(rep.mempool.max), diff --git a/src/ui/lvgl.zig b/src/ui/lvgl.zig index da441ad..0c5cadb 100644 --- a/src/ui/lvgl.zig +++ b/src/ui/lvgl.zig @@ -203,7 +203,7 @@ pub const LvStyle = opaque { /// produces an int value suitable for lv_xxx functions. fn value(self: Selector) c.lv_style_selector_t { - return @enumToInt(self.part) | @enumToInt(self.state); + return @intFromEnum(self.part) | @intFromEnum(self.state); } }; }; @@ -249,11 +249,11 @@ const RGB16 = packed struct { /// rgb produces a Color value base on the red, green and blue components. pub inline fn rgb(r: u8, g: u8, b: u8) Color { const c16 = RGB16{ - .b = @truncate(u5, b >> 3), - .g = @truncate(u6, g >> 2), - .r = @truncate(u5, r >> 3), + .b = @truncate(b >> 3), + .g = @truncate(g >> 2), + .r = @truncate(r >> 3), }; - return @bitCast(Color, c16); + return @bitCast(c16); } /// black color @@ -295,19 +295,19 @@ pub const Palette = enum(c.lv_palette_t) { /// returns main color from the predefined palette. pub inline fn main(p: Palette) Color { - return lv_palette_main(@enumToInt(p)); + return lv_palette_main(@intFromEnum(p)); } /// makes the main color from the predefined palette lighter according to the /// specified level. pub inline fn lighten(p: Palette, l: ModLevel) Color { - return lv_palette_lighten(@enumToInt(p), @enumToInt(l)); + return lv_palette_lighten(@intFromEnum(p), @intFromEnum(l)); } /// makes the main color from the predefined palette darker according to the /// specified level. pub inline fn darken(p: Palette, l: ModLevel) Color { - return lv_palette_darken(@enumToInt(p), @enumToInt(l)); + return lv_palette_darken(@intFromEnum(p), @intFromEnum(l)); } }; @@ -326,16 +326,16 @@ pub const BaseObjMethods = struct { /// sets or clears an object flag. pub fn setFlag(self: anytype, v: LvObj.Flag) void { - lv_obj_add_flag(self.lvobj, @enumToInt(v)); + lv_obj_add_flag(self.lvobj, @intFromEnum(v)); } pub fn clearFlag(self: anytype, v: LvObj.Flag) void { - lv_obj_clear_flag(self.lvobj, @enumToInt(v)); + lv_obj_clear_flag(self.lvobj, @intFromEnum(v)); } /// reports whether the object has v flag set. pub fn hasFlag(self: anytype, v: LvObj.Flag) bool { - return lv_obj_has_flag(self.lvobj, @enumToInt(v)); + return lv_obj_has_flag(self.lvobj, @intFromEnum(v)); } /// returns a user data pointer associated with the object. @@ -409,7 +409,7 @@ pub const WidgetMethods = struct { /// aligns object position. the offset is relative to the specified alignment a. pub fn posAlign(self: anytype, a: PosAlign, xoffset: Coord, yoffset: Coord) void { - lv_obj_align(self.lvobj, @enumToInt(a), xoffset, yoffset); + lv_obj_align(self.lvobj, @intFromEnum(a), xoffset, yoffset); } /// sets flex layout growth property; same meaning as in CSS flex. @@ -548,24 +548,24 @@ pub const FlexLayout = struct { } fn adopt(obj: *LvObj, flow: Flow, opt: AlignOpt) FlexLayout { - lv_obj_set_flex_flow(obj, @enumToInt(flow)); + lv_obj_set_flex_flow(obj, @intFromEnum(flow)); if (opt.all) |a| { - const v = @enumToInt(a); + const v = @intFromEnum(a); lv_obj_set_flex_align(obj, v, v, v); } else { - lv_obj_set_flex_align(obj, @enumToInt(opt.main), @enumToInt(opt.cross), @enumToInt(opt.track)); + lv_obj_set_flex_align(obj, @intFromEnum(opt.main), @intFromEnum(opt.cross), @intFromEnum(opt.track)); } return .{ .lvobj = obj }; } /// sets flex layout flow on the object. pub fn setFlow(self: FlexLayout, ff: Flow) void { - lv_obj_set_flex_flow(self.lvobj, @enumToInt(ff)); + lv_obj_set_flex_flow(self.lvobj, @intFromEnum(ff)); } /// sets flex layout alignments. pub fn setAlign(self: FlexLayout, main: Align, cross: AlignCross, track: Align) void { - lv_obj_set_flex_align(self.lvobj, @enumToInt(main), @enumToInt(cross), @enumToInt(track)); + lv_obj_set_flex_align(self.lvobj, @intFromEnum(main), @intFromEnum(cross), @intFromEnum(track)); } /// same as setPad .column but using a default constant to make flex layouts consistent. @@ -645,10 +645,10 @@ pub const Label = struct { lv_label_set_text(lv_label, text); //lv_obj_set_height(lb, sizeContent); // default if (opt.long_mode) |m| { - lv_label_set_long_mode(lv_label, @enumToInt(m)); + lv_label_set_long_mode(lv_label, @intFromEnum(m)); } if (opt.pos) |p| { - lv_obj_align(lv_label, @enumToInt(p), 0, 0); + lv_obj_align(lv_label, @intFromEnum(p), 0, 0); } if (opt.recolor) { lv_label_set_recolor(lv_label, true); diff --git a/src/ui/widget.zig b/src/ui/widget.zig index cce766d..185f466 100644 --- a/src/ui/widget.zig +++ b/src/ui/widget.zig @@ -67,12 +67,12 @@ pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const btncont.setHeightToContent(); // leave 5% as an extra spacing. - const btnwidth = lvgl.sizePercent(try std.math.divFloor(i16, 95, @truncate(u8, btns.len))); - for (btns) |btext, i| { + const btnwidth = lvgl.sizePercent(try std.math.divFloor(i16, 95, @as(u8, @truncate(btns.len)))); + for (btns, 0..) |btext, i| { const btn = try lvgl.TextButton.new(btncont, btext); btn.setFlag(.event_bubble); btn.setFlag(.user1); // .user1 indicates actionable button in callback - btn.setUserdata(@intToPtr(?*anyopaque, i)); // button index in callback + btn.setUserdata(@ptrFromInt(i)); // button index in callback btn.setWidth(btnwidth); if (i == 0) { btn.addStyle(lvgl.nm_style_btn_red(), .{}); @@ -88,9 +88,9 @@ export fn nm_modal_callback(e: *lvgl.LvEvent) void { return; } - const btn_index = @ptrToInt(target.userdata()); - const win = lvgl.Window{ .lvobj = @ptrCast(*lvgl.LvObj, edata) }; - const cb = @ptrCast(ModalButtonCallbackFn, @alignCast(@alignOf(ModalButtonCallbackFn), win.userdata())); + const btn_index = @intFromPtr(target.userdata()); + const win = lvgl.Window{ .lvobj = @ptrCast(edata) }; + const cb: ModalButtonCallbackFn = @alignCast(@ptrCast(win.userdata())); win.destroy(); cb(btn_index); } diff --git a/src/xfmt.zig b/src/xfmt.zig index c55562f..db89cf4 100644 --- a/src/xfmt.zig +++ b/src/xfmt.zig @@ -14,7 +14,7 @@ fn formatUnix(sec: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w if (sec > std.math.maxInt(u47)) { // EpochSeconds.getEpochDay trucates to u47 which results in a "truncated bits" // panic for too big numbers. so, just print raw digits. - return std.fmt.format(w, "{d}", .{sec}); + return std.fmt.formatInt(sec, 10, .lower, .{}, w); } const epoch: std.time.epoch.EpochSeconds = .{ .secs = sec }; const daysec = epoch.getDaySeconds(); diff --git a/tools/ci-containerfile b/tools/ci-containerfile index 036acaf..021587d 100644 --- a/tools/ci-containerfile +++ b/tools/ci-containerfile @@ -1,12 +1,12 @@ # ci container file for compiling and testing zig projects. # requires a ZIGURL build arg. for instance: -# podman build --rm -t ci-zig0.10.1 -f ci-containerfile \ -# --build-arg ZIGURL=https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz +# podman build --rm -t ci-zig0.11.0 -f ci-containerfile \ +# --build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz -FROM alpine:3.17.1 +FROM alpine:3.18.3 ARG ZIGURL -RUN apk add --no-cache git curl xz sdl2-dev clang15-extra-tools && \ +RUN apk add --no-cache git curl xz libc-dev sdl2-dev clang16-extra-tools && \ mkdir -p /tools/zig && \ cd /tools/zig && \ curl -o zig.tar.xz $ZIGURL && \