diff --git a/build.zig b/build.zig index 792e3b7..85928f3 100644 --- a/build.zig +++ b/build.zig @@ -24,6 +24,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + exe.linkLibC(); + exe.addIncludePath(.{ .cwd_relative = "/usr/include/glib-2.0" }); + exe.addIncludePath(.{ .cwd_relative = "/usr/lib/glib-2.0/include" }); + exe.addIncludePath(.{ .cwd_relative = "/usr/include/gdk-pixbuf-2.0" }); + exe.linkSystemLibrary("notify"); + // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). diff --git a/src/main.zig b/src/main.zig index 502322d..5ccbbb3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,10 +1,23 @@ const std = @import("std"); +const c = @cImport({ + @cInclude("libnotify/notify.h"); +}); pub const std_options = struct { pub const log_level = .info; }; +const NOTIFICATION_TITLE: [:0]const u8 = "bat-alert"; +const NOTIFICATION_BODY: [:0]const u8 = "battery running low!"; + +const ParseMode = enum { none, target, battery }; + pub fn main() !void { + if (c.notify_init("bat-alert") == c.FALSE) { + std.log.err("notify_init failed", .{}); + std.process.exit(1); + } + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); @@ -15,48 +28,57 @@ pub fn main() !void { var target: u8 = 15; var battery: []const u8 = "BAT0"; + var parse_mode = ParseMode.none; for (args, 0..) |arg, i| { - if (std.mem.eql(u8, arg, "--help")) { - const stdout = std.io.getStdOut().writer(); - try stdout.print( - \\Usage: bat-alert [options] - \\ - \\Options: - \\ -t, --target NUM Target battery level (default: 15) - \\ --battery NAME Battery name (default: BAT0) - \\ - , .{}); - std.process.exit(0); - } - - if (std.mem.eql(u8, arg, "--target") or std.mem.eql(u8, arg, "-t")) { - if (i + 1 == args.len) { - std.log.err("--target requires an argument", .{}); - std.process.exit(1); - } - - const parsedTarget = std.fmt.parseInt(u8, args[i + 1], 10) catch |err| { - std.log.err("invalid target '{s}': {}", .{ args[i + 1], err }); - std.process.exit(1); - }; - target = parsedTarget; - } - - if (std.mem.eql(u8, arg, "--battery")) { - if (i + 1 == args.len) { - std.log.err("--battery requires an argument", .{}); - std.process.exit(1); - } - - battery = args[i + 1]; + _ = i; + switch (parse_mode) { + ParseMode.none => { + if (std.mem.eql(u8, arg, "--help")) { + const stdout = std.io.getStdOut().writer(); + try stdout.print( + \\Usage: bat-alert [options] + \\ + \\Options: + \\ -t, --target NUM Target battery level (default: 15) + \\ --battery NAME Battery name (default: BAT0) + \\ + , .{}); + std.process.exit(0); + } + if (std.mem.eql(u8, arg, "--target") or std.mem.eql(u8, arg, "-t")) { + parse_mode = ParseMode.target; + } + if (std.mem.eql(u8, arg, "--battery")) { + parse_mode = ParseMode.battery; + } + }, + ParseMode.battery => { + battery = arg; + parse_mode = ParseMode.none; + }, + ParseMode.target => { + const parsedTarget = std.fmt.parseInt(u8, arg, 10) catch { + std.log.err("cannot parse target '{s}' as int", .{arg}); + std.process.exit(1); + }; + target = parsedTarget; + parse_mode = ParseMode.none; + }, } } + if (parse_mode != ParseMode.none) { + std.log.err("option requires an argument: --{s}", .{@tagName(parse_mode)}); + std.process.exit(1); + } var sent = false; const batteryPath = try std.fmt.allocPrint(allocator, "/sys/class/power_supply/{s}/capacity", .{battery}); while (true) { - const capacityFile = try std.fs.openFileAbsolute(batteryPath, .{}); + const capacityFile = std.fs.openFileAbsolute(batteryPath, .{}) catch |err| { + std.log.err("open {s}: {}", .{ batteryPath, err }); + std.process.exit(1); + }; var capacityStr = std.ArrayList(u8).init(allocator); try capacityFile.reader().streamUntilDelimiter(capacityStr.writer(), '\n', null); capacityFile.close(); @@ -69,10 +91,18 @@ pub fn main() !void { if (capacity <= target) { if (!sent) { sent = true; - _ = try std.process.Child.exec(.{ - .allocator = allocator, - .argv = &[_][]const u8{ "notify-send", "-t", "5000", "-u", "critical", "bat-alert", "battery running low!" }, - }); + // _ = try std.process.Child.exec(.{ + // .allocator = allocator, + // .argv = &[_][]const u8{ "notify-send", "-t", "5000", "-u", "critical", NOTIFICATION_TITLE, NOTIFICATION_BODY }, + // }); + + const notif = c.notify_notification_new(NOTIFICATION_TITLE, NOTIFICATION_BODY, null); + var g_err: [*c]c.GError = null; + const ret = c.notify_notification_show(notif, &g_err); + if (ret == c.FALSE) { + std.log.err("failed to send a notification: {s}", .{g_err.*.message}); + } + std.log.info("[NOTIFY] battery running low!", .{}); } } else {