Something went wrong. Try again.
Uploads documents to Paperless-ngx from the command line or from a Send to Paperless item in Nautilus.
Something went wrong. Try again.
4.6 kB · 115 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Desktop notifications, through the notification server on the session//! bus (the Desktop Notifications Specification's//! `org.freedesktop.Notifications`), called with the client zig-dbus-service//! generates from `dbus/org.freedesktop.Notifications.xml`.//!//! A notification is only ever a report: nothing here fails the work being//! reported on. A session with no bus, or no notification server, gets its//! messages on stderr instead.
const Notifier = @This();
const std = @import("std");const Io = std.Io;const Allocator = std.mem.Allocator;const mdbus = @import("mdbus");const dbus = @import("dbus_service");const Notifications = @import("dbus_interfaces").Notifications;
pub const app_name = "Paperless";pub const icon = "document-send";
gpa: Allocator,connection: ?mdbus.Connection,
pub const Urgency = enum(u8) { low = 0, normal = 1, critical = 2 };
/// Connects to the session bus at `address` (`DBUS_SESSION_BUS_ADDRESS`)./// A null address, or one that cannot be reached, leaves the notifier/// writing to stderr.pub fn init(io: Io, gpa: Allocator, address: ?[]const u8) Notifier { const connection: ?mdbus.Connection = if (address) |a| mdbus.Connection.connectAddress(io, gpa, a) catch |err| blk: { std.log.warn("cannot reach the session bus, notifications go to stderr: {t}", .{err}); break :blk null; } else null; return .{ .gpa = gpa, .connection = connection };}
pub fn deinit(self: *Notifier) void { if (self.connection) |*c| c.deinit(); self.* = undefined;}
/// The session bus's address, as `connectSession` would find it: the/// environment's `DBUS_SESSION_BUS_ADDRESS`, else the socket in/// `$XDG_RUNTIME_DIR`. Allocated in `arena`.pub fn sessionBusAddress(arena: Allocator, bus_address: ?[]const u8, runtime_dir: ?[]const u8) Allocator.Error!?[]const u8 { if (bus_address) |a| if (a.len > 0) return a; if (runtime_dir) |d| if (d.len > 0) return try std.fmt.allocPrint(arena, "unix:path={s}/bus", .{d}); return null;}
/// Shows a notification, or replaces the one `replaces` names, and answers/// with its identifier, to replace it in turn. Zero means there is none,/// and is always a safe thing to pass back.pub fn send(self: *Notifier, replaces: u32, urgency: Urgency, summary: []const u8, body: []const u8) u32 { if (self.connection) |*connection| { if (self.notify(connection, replaces, urgency, summary, body)) |id| return id else |err| { std.log.warn("notification not shown: {t}", .{err}); } } std.log.info("{s}: {s}", .{ summary, body }); return 0;}
fn notify(self: *Notifier, connection: *mdbus.Connection, replaces: u32, urgency: Urgency, summary: []const u8, body: []const u8) !u32 { var arena_state: std.heap.ArenaAllocator = .init(self.gpa); defer arena_state.deinit();
var client: Notifications.Client = .{ .connection = connection, .destination = "org.freedesktop.Notifications", .path = "/org/freedesktop/Notifications", .timeout_ms = 5_000, }; const level: u8 = @backingInt(urgency); const hints = [_]dbus.Vardict.Entry{ .{ .key = "urgency", .value = .of("y", &level) }, }; const out = try client.call(Notifications.Notify, arena_state.allocator(), .{ .app_name = app_name, .replaces_id = replaces, .app_icon = icon, .summary = summary, .body = body, .actions = &.{}, .hints = .{ .entries = &hints }, // The server's own default. .expire_timeout = -1, }); return out.id;}
const testing = std.testing;
test sessionBusAddress { var arena: std.heap.ArenaAllocator = .init(testing.allocator); defer arena.deinit(); const a = arena.allocator(); try testing.expectEqualStrings("unix:path=/x", (try sessionBusAddress(a, "unix:path=/x", "/run/user/1")).?); try testing.expectEqualStrings("unix:path=/run/user/1/bus", (try sessionBusAddress(a, null, "/run/user/1")).?); try testing.expectEqualStrings("unix:path=/run/user/1/bus", (try sessionBusAddress(a, "", "/run/user/1")).?); try testing.expectEqual(null, try sessionBusAddress(a, null, null));}
test "a notifier with no bus does not fail" { var notifier: Notifier = .init(testing.io, testing.allocator, null); defer notifier.deinit(); try testing.expectEqual(0, notifier.send(0, .normal, "summary", "body"));}