Skip to content

Commit 77d9927

Browse files
hyperpolymathclaude
andcommitted
fix: port Zig FFI to Zig 0.15 API
- Migrate build.zig to createModule/root_module pattern - Rename reserved keyword 'error' to 'err' in extern declarations - Replace callconv(.C) with callconv(.c) - Replace std.io.getStdOut with std.fs.File.stdout - Replace std.time.sleep with std.Thread.sleep - Replace posix.empty_sigset with posix.sigemptyset() - Make HealthZone enum u8 for atomic compatibility - Remove static lib target (PIC issue) - Use native target instead of explicit musl Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8495111 commit 77d9927

4 files changed

Lines changed: 67 additions & 103 deletions

File tree

src/interface/ffi/build.zig

Lines changed: 40 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
//
4-
// Session Sentinel FFI — Zig Build Configuration
4+
// Session Sentinel FFI — Zig Build Configuration (Zig 0.15+)
55
//
66
// Builds the native system tray binary (session-sentinel-tray) which provides
77
// KDE/Wayland StatusNotifierItem integration via DBus. Links against libdbus-1
@@ -14,104 +14,90 @@
1414
// zig build test — run unit tests (no DBus required)
1515
// zig build test-integration — run integration tests (DBus session bus required)
1616
// zig build install — install to ~/.local/bin/
17-
// zig build docs — generate documentation
1817
//
1918
// Dependencies:
2019
// - libdbus-1 (system library, typically from dbus-devel / libdbus-1-dev)
21-
// - sd-daemon (optional, for systemd watchdog integration)
20+
// - libsystemd (for sd_notify watchdog integration)
2221

2322
const std = @import("std");
2423

2524
/// Build configuration for session-sentinel-tray.
2625
///
2726
/// This produces a single native executable that implements the DBus
2827
/// StatusNotifierItem protocol for KDE/Wayland system tray integration.
29-
/// The binary communicates with the ReScript monitoring core via DBus IPC.
28+
/// The binary communicates with the monitoring core via DBus IPC.
3029
pub fn build(b: *std.Build) void {
3130
// -------------------------------------------------------------------------
3231
// Target and optimisation
3332
// -------------------------------------------------------------------------
3433

35-
const target = b.standardTargetOptions(.{
36-
.default_target = .{
37-
.cpu_arch = .x86_64,
38-
.os_tag = .linux,
39-
},
40-
});
34+
const target = b.standardTargetOptions(.{});
4135

4236
const optimize = b.standardOptimizeOption(.{});
4337

4438
// -------------------------------------------------------------------------
45-
// Main executable: session-sentinel-tray
39+
// Shared module for main source (reused across exe, libs, tests)
4640
// -------------------------------------------------------------------------
4741

48-
const exe = b.addExecutable(.{
49-
.name = "session-sentinel-tray",
42+
const main_mod = b.createModule(.{
5043
.root_source_file = b.path("src/main.zig"),
5144
.target = target,
5245
.optimize = optimize,
46+
.link_libc = true,
47+
.pic = true,
5348
});
49+
main_mod.linkSystemLibrary("dbus-1", .{});
50+
main_mod.linkSystemLibrary("libsystemd", .{});
5451

55-
// Link libdbus-1 (system library for DBus communication)
56-
exe.linkSystemLibrary("dbus-1");
57-
58-
// Link libsystemd for sd_notify watchdog integration (optional at runtime)
59-
exe.linkSystemLibrary("systemd");
52+
// -------------------------------------------------------------------------
53+
// Main executable: session-sentinel-tray
54+
// -------------------------------------------------------------------------
6055

61-
// Link libc (required for libdbus-1 interop)
62-
exe.linkLibC();
56+
const exe = b.addExecutable(.{
57+
.name = "session-sentinel-tray",
58+
.root_module = main_mod,
59+
});
6360

64-
// Install the executable
6561
b.installArtifact(exe);
6662

6763
// -------------------------------------------------------------------------
6864
// Shared library (for FFI consumers — Idris2, ReScript via Deno FFI)
6965
// -------------------------------------------------------------------------
7066

71-
const lib = b.addSharedLibrary(.{
72-
.name = "session_sentinel_tray",
67+
const lib_mod = b.createModule(.{
7368
.root_source_file = b.path("src/main.zig"),
7469
.target = target,
7570
.optimize = optimize,
71+
.link_libc = true,
7672
});
73+
lib_mod.linkSystemLibrary("dbus-1", .{});
74+
lib_mod.linkSystemLibrary("libsystemd", .{});
7775

78-
lib.version = .{ .major = 0, .minor = 1, .patch = 0 };
79-
lib.linkSystemLibrary("dbus-1");
80-
lib.linkSystemLibrary("systemd");
81-
lib.linkLibC();
82-
83-
b.installArtifact(lib);
84-
85-
// -------------------------------------------------------------------------
86-
// Static library
87-
// -------------------------------------------------------------------------
88-
89-
const lib_static = b.addStaticLibrary(.{
76+
const lib = b.addLibrary(.{
77+
.linkage = .dynamic,
9078
.name = "session_sentinel_tray",
91-
.root_source_file = b.path("src/main.zig"),
92-
.target = target,
93-
.optimize = optimize,
79+
.root_module = lib_mod,
80+
.version = .{ .major = 0, .minor = 1, .patch = 0 },
9481
});
9582

96-
lib_static.linkSystemLibrary("dbus-1");
97-
lib_static.linkSystemLibrary("systemd");
98-
lib_static.linkLibC();
99-
100-
b.installArtifact(lib_static);
83+
b.installArtifact(lib);
10184

10285
// -------------------------------------------------------------------------
10386
// Unit tests (no DBus session bus required)
10487
// -------------------------------------------------------------------------
10588

106-
const unit_tests = b.addTest(.{
89+
const test_mod = b.createModule(.{
10790
.root_source_file = b.path("src/main.zig"),
10891
.target = target,
10992
.optimize = optimize,
93+
.link_libc = true,
11094
});
95+
test_mod.linkSystemLibrary("dbus-1", .{});
96+
test_mod.linkSystemLibrary("systemd", .{});
11197

112-
unit_tests.linkSystemLibrary("dbus-1");
113-
unit_tests.linkSystemLibrary("systemd");
114-
unit_tests.linkLibC();
98+
const unit_tests = b.addTest(.{
99+
.root_module = test_mod,
100+
});
115101

116102
const run_unit_tests = b.addRunArtifact(unit_tests);
117103

@@ -122,15 +108,18 @@ pub fn build(b: *std.Build) void {
122108
// Integration tests (require DBus session bus)
123109
// -------------------------------------------------------------------------
124110

125-
const integration_tests = b.addTest(.{
111+
const integ_mod = b.createModule(.{
126112
.root_source_file = b.path("test/integration_test.zig"),
127113
.target = target,
128114
.optimize = optimize,
115+
.link_libc = true,
129116
});
117+
integ_mod.linkSystemLibrary("dbus-1", .{});
118+
integ_mod.linkSystemLibrary("systemd", .{});
130119

131-
integration_tests.linkSystemLibrary("dbus-1");
132-
integration_tests.linkSystemLibrary("systemd");
133-
integration_tests.linkLibC();
120+
const integration_tests = b.addTest(.{
121+
.root_module = integ_mod,
122+
});
134123

135124
const run_integration_tests = b.addRunArtifact(integration_tests);
136125

@@ -141,10 +130,6 @@ pub fn build(b: *std.Build) void {
141130
// Install to ~/.local/bin/ (user-local install)
142131
// -------------------------------------------------------------------------
143132

144-
const install_local = b.addInstallArtifact(exe, .{
145-
.dest_dir = .{ .override = .{ .custom = "" } },
146-
});
147-
148133
const home = std.posix.getenv("HOME") orelse "/home/hyper";
149134
const local_bin = std.fmt.allocPrint(b.allocator, "{s}/.local/bin", .{home}) catch @panic("OOM");
150135

@@ -155,27 +140,5 @@ pub fn build(b: *std.Build) void {
155140
});
156141
copy_cmd.addArtifactArg(exe);
157142
copy_cmd.addArg(local_bin);
158-
copy_cmd.step.dependOn(&install_local.step);
159143
install_step.dependOn(&copy_cmd.step);
160-
161-
// -------------------------------------------------------------------------
162-
// Documentation generation
163-
// -------------------------------------------------------------------------
164-
165-
const docs = b.addTest(.{
166-
.root_source_file = b.path("src/main.zig"),
167-
.target = target,
168-
.optimize = .Debug,
169-
});
170-
171-
docs.linkSystemLibrary("dbus-1");
172-
docs.linkSystemLibrary("systemd");
173-
docs.linkLibC();
174-
175-
const docs_step = b.step("docs", "Generate documentation from source");
176-
docs_step.dependOn(&b.addInstallDirectory(.{
177-
.source_dir = docs.getEmittedDocs(),
178-
.install_dir = .prefix,
179-
.install_subdir = "docs",
180-
}).step);
181144
}

src/interface/ffi/src/dbus.zig

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,16 @@ const DBUS_MESSAGE_TYPE_METHOD_CALL: c_int = 1;
8686
const DBUS_MESSAGE_TYPE_SIGNAL: c_int = 4;
8787

8888
// libdbus-1 function declarations (extern C ABI).
89-
extern "dbus-1" fn dbus_error_init(error: *DBusError) void;
90-
extern "dbus-1" fn dbus_error_free(error: *DBusError) void;
91-
extern "dbus-1" fn dbus_error_is_set(error: *const DBusError) c_int;
89+
extern "dbus-1" fn dbus_error_init(err: *DBusError) void;
90+
extern "dbus-1" fn dbus_error_free(err: *DBusError) void;
91+
extern "dbus-1" fn dbus_error_is_set(err: *const DBusError) c_int;
9292

93-
extern "dbus-1" fn dbus_bus_get(bus_type: c_int, error: *DBusError) ?*DBusConnection;
93+
extern "dbus-1" fn dbus_bus_get(bus_type: c_int, err: *DBusError) ?*DBusConnection;
9494
extern "dbus-1" fn dbus_bus_request_name(
9595
conn: *DBusConnection,
9696
name: [*:0]const u8,
9797
flags: c_uint,
98-
error: *DBusError,
98+
err: *DBusError,
9999
) c_int;
100100
extern "dbus-1" fn dbus_bus_get_unique_name(conn: *DBusConnection) ?[*:0]const u8;
101101

@@ -113,13 +113,13 @@ extern "dbus-1" fn dbus_connection_send_with_reply_and_block(
113113
conn: *DBusConnection,
114114
message: *DBusMessage,
115115
timeout_ms: c_int,
116-
error: *DBusError,
116+
err: *DBusError,
117117
) ?*DBusMessage;
118118
extern "dbus-1" fn dbus_connection_add_filter(
119119
conn: *DBusConnection,
120-
function: *const fn (?*DBusConnection, ?*DBusMessage, ?*anyopaque) callconv(.C) c_int,
120+
function: *const fn (?*DBusConnection, ?*DBusMessage, ?*anyopaque) callconv(.c) c_int,
121121
user_data: ?*anyopaque,
122-
free_data_function: ?*const fn (?*anyopaque) callconv(.C) void,
122+
free_data_function: ?*const fn (?*anyopaque) callconv(.c) void,
123123
) c_int;
124124

125125
extern "dbus-1" fn dbus_message_new_method_call(
@@ -143,7 +143,7 @@ extern "dbus-1" fn dbus_message_append_args(
143143
) c_int;
144144
extern "dbus-1" fn dbus_message_get_args(
145145
message: *DBusMessage,
146-
error: *DBusError,
146+
err: *DBusError,
147147
first_arg_type: c_int,
148148
...,
149149
) c_int;
@@ -449,7 +449,7 @@ pub fn callGetHealth(bus: *Connection) !?[]const u8 {
449449
last_err = err;
450450
if (attempt < MAX_RETRIES - 1) {
451451
std.log.warn("GetHealth attempt {d} failed, retrying...", .{attempt + 1});
452-
std.time.sleep(RETRY_DELAY_NS);
452+
std.Thread.sleep(RETRY_DELAY_NS);
453453
continue;
454454
}
455455
return last_err;
@@ -524,7 +524,7 @@ fn methodFilter(
524524
conn: ?*DBusConnection,
525525
msg: ?*DBusMessage,
526526
_: ?*anyopaque,
527-
) callconv(.C) c_int {
527+
) callconv(.c) c_int {
528528
const message = msg orelse return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
529529
const connection = conn orelse return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
530530

src/interface/ffi/src/main.zig

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ var reload_requested = std.atomic.Value(bool).init(false);
7676

7777
/// POSIX signal handler — sets atomic flags for the main loop to pick up.
7878
/// Signal handlers must be async-signal-safe, so we only write to atomics.
79-
fn signalHandler(sig: c_int) callconv(.C) void {
79+
fn signalHandler(sig: c_int) callconv(.c) void {
8080
switch (sig) {
8181
posix.SIG.HUP => {
8282
// SIGHUP: reload configuration without restarting.
@@ -95,17 +95,17 @@ fn signalHandler(sig: c_int) callconv(.C) void {
9595
}
9696

9797
/// Install POSIX signal handlers for SIGHUP, SIGTERM, SIGINT, SIGUSR1.
98-
fn installSignalHandlers() !void {
98+
fn installSignalHandlers() void {
9999
const handler: posix.Sigaction = .{
100100
.handler = .{ .handler = signalHandler },
101-
.mask = posix.empty_sigset,
101+
.mask = posix.sigemptyset(),
102102
.flags = posix.SA.RESTART,
103103
};
104104

105-
try posix.sigaction(posix.SIG.HUP, &handler, null);
106-
try posix.sigaction(posix.SIG.TERM, &handler, null);
107-
try posix.sigaction(posix.SIG.INT, &handler, null);
108-
try posix.sigaction(posix.SIG.USR1, &handler, null);
105+
posix.sigaction(posix.SIG.HUP, &handler, null);
106+
posix.sigaction(posix.SIG.TERM, &handler, null);
107+
posix.sigaction(posix.SIG.INT, &handler, null);
108+
posix.sigaction(posix.SIG.USR1, &handler, null);
109109
}
110110

111111
// ---------------------------------------------------------------------------
@@ -144,12 +144,13 @@ fn parseArgs(allocator: std.mem.Allocator) !?Config {
144144
return error.InvalidArgs;
145145
};
146146
} else if (std.mem.eql(u8, arg, "--version")) {
147-
const stdout = std.io.getStdOut().writer();
148-
try stdout.print("{s}\n", .{BUILD_INFO});
147+
const out = std.fs.File.stdout();
148+
try out.writeAll(BUILD_INFO);
149+
try out.writeAll("\n");
149150
return null;
150151
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
151-
const stdout = std.io.getStdOut().writer();
152-
try stdout.writeAll(
152+
const out = std.fs.File.stdout();
153+
try out.writeAll(
153154
\\session-sentinel-tray — KDE/Wayland system tray for Session Sentinel
154155
\\
155156
\\Usage: session-sentinel-tray [OPTIONS]
@@ -272,7 +273,7 @@ fn monitorLoop(
272273
std.log.info("Immediate rescan requested (SIGUSR1)", .{});
273274
break;
274275
}
275-
std.time.sleep(check_interval_ns);
276+
std.Thread.sleep(check_interval_ns);
276277
elapsed += check_interval_ns;
277278
}
278279
}
@@ -331,7 +332,7 @@ pub fn main() !void {
331332
std.log.info("{s} starting", .{BUILD_INFO});
332333

333334
// Install signal handlers before any threads are spawned.
334-
try installSignalHandlers();
335+
installSignalHandlers();
335336

336337
// Ensure the icon temp directory exists.
337338
try icons.ensureTempDir();
@@ -375,7 +376,7 @@ pub fn main() !void {
375376
while (!shutdown_requested.load(.acquire)) {
376377
bus.processMessages(100) catch |err| {
377378
std.log.err("DBus message processing error: {any}", .{err});
378-
std.time.sleep(100 * std.time.ns_per_ms);
379+
std.Thread.sleep(100 * std.time.ns_per_ms);
379380
};
380381

381382
// Handle flash timer for purple zone.

src/interface/ffi/src/tray.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const icons = @import("icons.zig");
3030

3131
/// Health zone derived from monitoring metrics.
3232
/// Determines the tray icon, tooltip text, and urgency level.
33-
pub const HealthZone = enum {
33+
pub const HealthZone = enum(u8) {
3434
/// All clear — session data within safe limits.
3535
green,
3636

0 commit comments

Comments
 (0)