fix proxy, add remaining cli functionality

This commit is contained in:
Aylur
2024-05-21 22:08:41 +02:00
parent 5929e35962
commit 81c4e3a12d
8 changed files with 186 additions and 104 deletions
-4
View File
@@ -5,7 +5,3 @@ A notification daemon library and cli tool
## TODO
- docs
- cli options for
- [ ] getting a list of every notification
- [ ] getting a notification by its id
- [ ] invoke notification action by id
+1 -1
View File
@@ -27,7 +27,7 @@
default = notifd;
notifd = pkgs.stdenv.mkDerivation {
inherit nativeBuildInputs buildInputs;
pname = "notifd";
pname = "astal-notifd";
version = version;
src = ./.;
outputs = ["out" "dev"];
+74 -14
View File
@@ -1,9 +1,19 @@
private static bool version;
private static bool help;
static bool help;
static bool version;
static bool daemonize;
static bool list;
static string invoke;
static int close_n;
static int get_n;
private const OptionEntry[] options = {
{ "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null },
{ "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null },
{ "daemon", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, null, null },
{ "list", 'l', OptionFlags.NONE, OptionArg.NONE, ref list, null, null },
{ "invoke", 'i', OptionFlags.NONE, OptionArg.STRING, ref invoke, null, null },
{ "close", 'c', OptionFlags.NONE, OptionArg.INT, ref close_n, null, null },
{ "get", 'g', OptionFlags.NONE, OptionArg.INT, ref get_n, null, null },
{ null },
};
@@ -22,26 +32,76 @@ int main(string[] argv) {
if (help) {
print("Cli client for astal-notifd\n\n");
print("Usage:\n");
print(" %s [flags] message\n\n", argv[0]);
print(" %s [flags]\n\n", argv[0]);
print("Flags:\n");
print(" -h, --help Print this help and exit\n");
print(" -v, --version Print version number and exit\n");
print(" -h, --help Print this help and exit\n");
print(" -v, --version Print version number and exit\n");
print(" -l, --list Print every notification and exit\n");
print(" -d, --daemonize Watch for new notifications\n");
print(" -i, --invoke Invoke a notification action\n");
print(" -c, --close Close a notification by its id\n");
print(" -g, --get Print a notification by its id\n");
return 0;
}
var loop = new MainLoop();
var notifd = new AstalNotifd.Notifd();
if (version) {
print("@VERSION@");
return 0;
}
var notifd = new AstalNotifd.Notifd();
notifd.notified.connect((id) => {
stdout.printf("%s\n", notifd.get_notification_json(id));
});
notifd.active.connect(() => {
foreach (var n in notifd.notifications)
stdout.printf("%s\n", n.to_json_string());
});
new MainLoop().run();
if (list) {
var cache = Environment.get_user_cache_dir() + "/astal/notifd/notifications.json";
if (FileUtils.test(cache, FileTest.EXISTS)) {
try {
uint8[] json;
File.new_for_path(cache).load_contents(null, out json, null);
stdout.printf("%s", (string)json);
} catch (Error err) {
stderr.printf("failed to load cache: %s", err.message);
}
}
return 0;
}
if (daemonize) {
notifd.notified.connect((id) => {
stdout.printf("%s\n", notifd.get_notification_json(id));
});
}
if (invoke != null) {
if (!invoke.contains(":")) {
stderr.printf("invoke format needs to be <notif-id>:<action-id>");
return 1;
}
var split = invoke.split(":");
var n_id = int.parse(split[0]);
var a_id = split[1];
notifd.active.connect(() => {
notifd.get_notification(n_id).invoke(a_id);
loop.quit();
});
}
if (close_n > 0) {
notifd.active.connect(() => {
notifd.get_notification(close_n).dismiss();
loop.quit();
});
}
if (get_n > 0) {
notifd.active.connect(() => {
stdout.printf("%s", notifd.get_notification(get_n).to_json_string());
loop.quit();
});
}
loop.run();
return 0;
}
+20 -14
View File
@@ -13,21 +13,26 @@ internal class Daemon : Object {
public static string version = "0.1";
private string cache_file;
private string cache_directory;
private uint n_id = 1;
private HashTable<uint, Notification> notifs =
new HashTable<uint, Notification>((i) => i, (a, b) => a == b);
public string cache_directory { set; owned get; }
public bool ignore_timeout { get; set; }
public bool dont_disturb { get; set; }
public signal void notified(uint id);
public signal void resolved(uint id, ClosedReason reason);
public signal void action_invoked(uint id, string action);
// emitting an event from proxy doesn't seem to work
public void emit_notified(uint id) { notified(id); }
public void emit_resolved(uint id, ClosedReason reason) { resolved(id, reason); }
public void emit_action_invoked(uint id, string action) { action_invoked(id, action); }
construct {
if (cache_directory == null)
cache_directory = Environment.get_user_cache_dir() + "/astal/notifd";
cache_directory = Environment.get_user_cache_dir() + "/astal/notifd";
cache_file = cache_directory + "/notifications.json";
if (FileUtils.test(cache_file, FileTest.EXISTS)) {
@@ -38,8 +43,7 @@ internal class Daemon : Object {
parser.load_from_data((string)json);
var list = parser.get_root().get_array();
for (var i = 0; i < list.get_length(); ++i) {
var n = new Notification.from_json(list.get_object_element(i));
notifs.set(n.id, n);
add_notification(new Notification.from_json(list.get_object_element(i)));
}
} catch (Error err) {
warning("failed to load cache: %s", err.message);
@@ -73,7 +77,7 @@ internal class Daemon : Object {
}
[DBus (visible = false)]
public Notification get_notification(uint id) throws DBusError, IOError {
public Notification get_notification(uint id) {
return notifs.get(id);
}
@@ -105,12 +109,10 @@ internal class Daemon : Object {
hints.remove("icon_data");
var id = replaces_id > 0 ? replaces_id : n_id++;
var n = new Notification(
app_name, id, app_icon, summary, body, actions, hints, expire_timeout
);
n.dismissed.connect(() => resolved(id, ClosedReason.DISMISSED_BY_USER));
n.invoked.connect((action) => action_invoked(id, action));
add_notification(new Notification(
app_name, id, app_icon, summary, body, actions, hints, expire_timeout
));
if (!ignore_timeout && expire_timeout > 0) {
Timeout.add(expire_timeout, () => {
@@ -119,7 +121,6 @@ internal class Daemon : Object {
}, Priority.DEFAULT);
}
notifs.set(id, n);
if (!dont_disturb)
notified(id);
@@ -127,6 +128,12 @@ internal class Daemon : Object {
return id;
}
private void add_notification(Notification n) {
n.dismissed.connect(() => resolved(n.id, ClosedReason.DISMISSED_BY_USER));
n.invoked.connect((action) => action_invoked(n.id, action));
notifs.set(n.id, n);
}
private void cache() {
var list = new Json.Builder().begin_array();
foreach (var n in notifications) {
@@ -148,7 +155,6 @@ internal class Daemon : Object {
}
public signal void notification_closed(uint id, uint reason);
public signal void action_invoked(uint id, string action);
public signal void activation_token(uint id, string token);
public void close_notification(uint id) throws DBusError, IOError {
+5 -50
View File
@@ -8,23 +8,8 @@ public class Notifd : Object {
private Daemon daemon;
private DaemonProxy proxy;
private HashTable<uint, Notification> notifs =
new HashTable<uint, Notification>((i) => i, (a, b) => a == b);
public signal void active(ActiveType type);
public string cache_directory {
owned get {
return proxy != null ? proxy.cache_directory : daemon.cache_directory;
}
set {
if (proxy != null)
proxy.cache_directory = value;
else
daemon.cache_directory = value;
}
}
public bool ignore_timeout {
get {
return proxy != null ? proxy.ignore_timeout : daemon.ignore_timeout;
@@ -50,7 +35,7 @@ public class Notifd : Object {
}
public List<weak Notification> notifications {
owned get { return notifs.get_values(); }
owned get { return proxy != null ? proxy.notifications : daemon.notifications; }
}
public uint[] notification_ids() throws Error {
@@ -58,33 +43,15 @@ public class Notifd : Object {
}
public Notification get_notification(uint id) {
return notifs.get(id);
return proxy != null ? proxy.get_notification(id) : daemon.get_notification(id);
}
public string get_notification_json(uint id) {
return notifs.get(id).to_json_string();
return get_notification(id).to_json_string();
}
public signal void notified(uint id) {
add_notification(id);
}
public signal void resolved(uint id, ClosedReason reason) {
notifs.remove(id);
}
private void add_notification(uint id) {
try {
if (proxy != null) {
var json = proxy.get_notification_json(id);
notifs.set(id, Notification.from_json_string(json));
} else {
notifs.set(id, daemon.get_notification(id));
}
} catch (Error err) {
warning("could not add notification: %s", err.message);
}
}
public signal void notified(uint id);
public signal void resolved(uint id, ClosedReason reason);
construct {
Bus.own_name(
@@ -112,23 +79,11 @@ public class Notifd : Object {
notify_property(prop.name);
}
});
foreach (var n in daemon.notifications) {
notifs.set(n.id, n);
}
}
private void try_proxy() {
proxy = new DaemonProxy();
if (proxy.start()) {
try {
foreach (var id in proxy.notification_ids()) {
add_notification(id);
}
}
catch (Error err) {
warning("could not get notification ids: %s", err.message);
}
active(ActiveType.PROXY);
} else {
return;
+3 -3
View File
@@ -90,7 +90,7 @@ public class Notification : Object {
public void dismiss() { dismissed(); }
public void invoke(string action) { invoked(action); }
public Notification.from_json(Json.Object root) throws GLib.Error {
internal Notification.from_json(Json.Object root) throws GLib.Error {
foreach (var key in root.get_members()) {
var node = root.get_member(key);
switch (key) {
@@ -123,7 +123,7 @@ public class Notification : Object {
}
}
public static Notification from_json_string(string json) throws GLib.Error {
internal static Notification from_json_string(string json) throws GLib.Error {
var parser = new Json.Parser();
parser.load_from_data(json);
return new Notification.from_json(parser.get_root().get_object());
@@ -135,7 +135,7 @@ public class Notification : Object {
return generator.to_data(null);
}
public Json.Node to_json() {
internal Json.Node to_json() {
var acts = new Json.Builder().begin_array();
foreach (var action in actions) {
acts.begin_object()
+51 -18
View File
@@ -1,7 +1,6 @@
namespace AstalNotifd {
[DBus (name = "org.freedesktop.Notifications")]
internal interface IDaemon : Object {
public abstract string cache_directory { owned get; set; }
public abstract bool ignore_timeout { get; set; }
public abstract bool dont_disturb { get; set; }
@@ -10,12 +9,19 @@ internal interface IDaemon : Object {
public signal void notified(uint id);
public signal void resolved(uint id, ClosedReason reason);
public signal void action_invoked(uint id, string action);
public abstract void emit_notified(uint id);
public abstract void emit_resolved(uint id, ClosedReason reason);
public abstract void emit_action_invoked(uint id, string action);
}
internal class DaemonProxy : Object {
public string cache_directory {
owned get { return proxy.cache_directory; }
set { proxy.cache_directory = value; }
private HashTable<uint, Notification> notifs =
new HashTable<uint, Notification>((i) => i, (a, b) => a == b);
public List<weak Notification> notifications {
owned get { return notifs.get_values(); }
}
public bool ignore_timeout {
@@ -32,8 +38,8 @@ internal class DaemonProxy : Object {
return proxy.notification_ids();
}
public string get_notification_json(uint id) throws DBusError, IOError {
return proxy.get_notification_json(id);
public Notification get_notification(uint id) {
return notifs.get(id);
}
public signal void notified(uint id);
@@ -72,18 +78,7 @@ internal class DaemonProxy : Object {
&& version == Daemon.version;
if (running) {
proxy = Bus.get_proxy_sync(
BusType.SESSION,
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications"
);
ids.append(proxy.notified.connect((id) => notified(id)));
ids.append(proxy.resolved.connect((id, reason) => resolved(id, reason)));
ids.append(proxy.notify.connect((pspec) => {
if (get_class().find_property(pspec.name) != null)
notify_property(pspec.name);
}));
setup_proxy();
return true;
} else {
critical("cannot get proxy: %s is already running", name);
@@ -93,5 +88,43 @@ internal class DaemonProxy : Object {
}
return false;
}
private void setup_proxy() throws Error {
proxy = Bus.get_proxy_sync(
BusType.SESSION,
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications"
);
foreach (var id in proxy.notification_ids())
add_notification(id);
ids.append(proxy.notify.connect((pspec) => {
if (get_class().find_property(pspec.name) != null)
notify_property(pspec.name);
}));
ids.append(proxy.notified.connect((id) => {
add_notification(id);
notified(id);
}));
ids.append(proxy.resolved.connect((id, reason) => {
notifs.remove(id);
resolved(id, reason);
}));
}
private void add_notification(uint id) {
try {
var n = Notification.from_json_string(proxy.get_notification_json(id));
proxy.resolved.connect((id, reason) => n.resolved(reason));
n.dismissed.connect(() => proxy.emit_resolved(id, ClosedReason.DISMISSED_BY_USER));
n.invoked.connect((action) => proxy.emit_action_invoked(id, action));
notifs.set(id, n);
} catch (Error err) {
critical(err.message);
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
# Signals
ignore this, I'm just dumb and can't follow where signals go or get emitted from
## Notification
* resolved(reason) - by daemon/proxy
* dismissed() - by user with `.dismiss()`
* invoked(action) - by user with `.invoke()`
## Deamon
non-spec, used by user
* notified(id) - by outside through dbus with `.Notify()`
* resolved(id, reason) - by `Notification.dismiss()` or outside with `.CloseNotification`
spec, not used by user
* notification_closed(id, reason) - sideeffect of `resolved`
* action_invoked(id, action) - by `Notification.invoke()`
## Proxy
mirrors Daemon
* notified(id)
* resolved(id, reason)
creates `Notification` objects through daemon's json strings
and hooks them up to call daemon's signals and vice versa
## Notifd
acts as a bridge between Proxy/Daemon, everything else is internal only