This commit is contained in:
LordMZTE 2023-02-06 00:28:41 +01:00
commit f6cf0c6dc6
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
7 changed files with 185 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
zig-*

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "deps/ws"]
path = deps/ws
url = https://github.com/nikneym/ws.git

43
build.zig Normal file
View file

@ -0,0 +1,43 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "ntfylisten",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const zuri_mod = b.createModule(.{
.source_file = .{ .path = "deps/ws/lib/zuri/src/zuri.zig" },
});
exe.addAnonymousModule("ws", .{
.source_file = .{ .path = "deps/ws/src/main.zig" },
.dependencies = &.{.{ .name = "zuri", .module = zuri_mod }},
});
// need libnotify, so might as well link glib and use glib logging
exe.linkLibC();
exe.linkSystemLibrary("glib-2.0");
// needed for libnotify
exe.linkSystemLibrary("gdk-pixbuf-2.0");
exe.linkSystemLibrary("notify");
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

1
deps/ws vendored Submodule

@ -0,0 +1 @@
Subproject commit d11513b70fa7b062bfa327af3377436a86d05b89

4
src/Message.zig Normal file
View file

@ -0,0 +1,4 @@
event: []const u8,
message: ?[:0]const u8 = null,
title: ?[]const u8 = null,
priority: u8 = 4,

4
src/ffi.zig Normal file
View file

@ -0,0 +1,4 @@
pub const c = @cImport({
@cInclude("libnotify/notify.h");
@cInclude("glib.h");
});

129
src/main.zig Normal file
View file

@ -0,0 +1,129 @@
const std = @import("std");
const ws = @import("ws");
const c = @import("ffi.zig").c;
const log = std.log.scoped(.ntfylisten);
const Message = @import("Message.zig");
pub const std_options = struct {
// level filtering handled by glib
pub const log_level = .debug;
pub fn logFn(
comptime message_level: std.log.Level,
comptime scope: @TypeOf(.enum_literal),
comptime format: []const u8,
args: anytype,
) void {
var printbuf: [1024 * 4]u8 = undefined;
const msg = std.fmt.bufPrint(&printbuf, format, args) catch return;
const level = switch (message_level) {
.debug => c.G_LOG_LEVEL_DEBUG,
.info => c.G_LOG_LEVEL_INFO,
.warn => c.G_LOG_LEVEL_WARNING,
.err => c.G_LOG_LEVEL_CRITICAL,
};
const domain = @tagName(scope);
const fields = [_]c.GLogField{
.{
.key = "GLIB_DOMAIN",
.value = domain,
.length = @intCast(c.gssize, domain.len),
},
.{
.key = "MESSAGE",
.value = msg.ptr,
.length = @intCast(c.gssize, msg.len),
},
};
c.g_log_structured_array(level, &fields, fields.len);
}
};
pub fn main() !void {
if (std.os.argv.len != 4) {
log.err(
\\Expected 3 arguments!
\\Usage:
\\ {s} [server] [topic] [title_prefix]
,
.{std.os.argv[0]},
);
std.os.exit(1);
}
const server = std.mem.span(std.os.argv[1]);
const topic = std.mem.span(std.os.argv[2]);
const title_prefix = std.mem.span(std.os.argv[3]);
if (c.notify_init("ntfylisten") == 0)
return error.LibnotifyInit;
const addr = try std.fmt.allocPrint(std.heap.c_allocator, "ws://{s}/{s}/ws", .{ server, topic });
defer std.heap.c_allocator.free(addr);
log.info("connecting to WebSocket URL {s}", .{addr});
var ws_client = try ws.connect(std.heap.c_allocator, addr, &.{
.{ "Host", server },
});
defer ws_client.deinit(std.heap.c_allocator);
while (true) {
var msg = try ws_client.receive();
switch (msg.type) {
.text, .binary => {
var ts = std.json.TokenStream.init(msg.data);
const data = std.json.parse(
Message,
&ts,
.{ .allocator = std.heap.c_allocator, .ignore_unknown_fields = true },
) catch |e| {
log.warn("invalid data from server: {}", .{e});
continue;
};
defer std.json.parseFree(Message, data, .{ .allocator = std.heap.c_allocator });
if (!std.mem.eql(u8, data.event, "message"))
continue;
const text = data.message orelse continue;
const title = if (data.title) |title|
try std.fmt.allocPrintZ(std.heap.c_allocator, "{s}: {s}", .{ title_prefix, title })
else
try std.heap.c_allocator.dupeZ(u8, title_prefix);
defer std.heap.c_allocator.free(title);
const urgency = switch (std.math.order(data.priority, 3)) {
.lt => c.NOTIFY_URGENCY_LOW,
.eq => c.NOTIFY_URGENCY_NORMAL,
.gt => c.NOTIFY_URGENCY_CRITICAL,
};
const notif = c.notify_notification_new(title, text, null);
c.notify_notification_set_urgency(notif, @intCast(c_uint, urgency));
_ = c.notify_notification_show(notif, null);
},
.ping => {
try ws_client.pong();
},
.close => {
log.warn("server sent close message, exiting", .{});
break;
},
else => {},
}
}
try ws_client.close();
}