diff options
-rw-r--r-- | src/cli.vala | 54 | ||||
-rw-r--r-- | src/daemon.vala | 86 | ||||
-rw-r--r-- | src/notifd.vala | 44 | ||||
-rw-r--r-- | src/notification.vala | 33 | ||||
-rw-r--r-- | src/proxy.vala | 23 |
5 files changed, 142 insertions, 98 deletions
diff --git a/src/cli.vala b/src/cli.vala index 433ba43..afce774 100644 --- a/src/cli.vala +++ b/src/cli.vala @@ -5,6 +5,7 @@ static bool list; static string invoke; static int close_n; static int get_n; +static bool toggle_dnd; const OptionEntry[] options = { { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, @@ -14,6 +15,7 @@ const OptionEntry[] options = { { "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 }, + { "toggle-dnd", 't', OptionFlags.NONE, OptionArg.NONE, ref toggle_dnd, null, null }, { null }, }; @@ -34,17 +36,17 @@ int main(string[] argv) { print("Usage:\n"); 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(" -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"); + 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"); + print(" -t, --toggle-dnd Toggle do not disturb\n"); return 0; } - var loop = new MainLoop(); var notifd = new AstalNotifd.Notifd(); if (version) { @@ -53,16 +55,27 @@ int main(string[] argv) { } if (list) { - var cache = Environment.get_user_cache_dir() + "/astal/notifd/notifications.json"; - if (FileUtils.test(cache, FileTest.EXISTS)) { + var state = Environment.get_user_state_dir() + "/astal/notifd/notifications.json"; + if (FileUtils.test(state, FileTest.EXISTS)) { try { uint8[] json; - File.new_for_path(cache).load_contents(null, out json, null); - stdout.printf("%s", (string)json); + File.new_for_path(state).load_contents(null, out json, null); + + var obj = Json.from_string((string)json); + + var list = obj.get_object().get_member("notifications"); + stdout.printf("%s\n", Json.to_string(list, true)); + return 0; } catch (Error err) { stderr.printf("failed to load cache: %s", err.message); } } + stdout.printf("[]\n"); + return 0; + } + + if (toggle_dnd) { + notifd.dont_disturb = !notifd.dont_disturb; return 0; } @@ -71,6 +84,7 @@ int main(string[] argv) { stdout.printf("%s\n", notifd.get_notification_json(id)); stdout.flush(); }); + new MainLoop().run(); } if (invoke != null) { @@ -83,29 +97,19 @@ int main(string[] argv) { 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(); - }); + notifd.get_notification(n_id).invoke(a_id); } if (close_n > 0) { - notifd.active.connect(() => { - notifd.get_notification(close_n).dismiss(); - loop.quit(); - }); + notifd.get_notification(close_n).dismiss(); } if (get_n > 0) { - notifd.active.connect(() => { - stdout.printf("%s", notifd.get_notification(get_n).to_json_string()); - loop.quit(); - }); + stdout.printf("%s", notifd.get_notification(get_n).to_json_string()); } if (!daemonize && invoke == null && close_n == 0 && get_n == 0) return 1; - loop.run(); return 0; } diff --git a/src/daemon.vala b/src/daemon.vala index fe38364..b8fb598 100644 --- a/src/daemon.vala +++ b/src/daemon.vala @@ -1,30 +1,39 @@ -namespace AstalNotifd { -public enum ClosedReason { - EXPIRED = 1, - DISMISSED_BY_USER = 2, - CLOSED = 3, - UNDEFINED = 4, -} - [DBus (name = "org.freedesktop.Notifications")] -internal class Daemon : Object { +internal class AstalNotifd.Daemon : Object { public static string name = "notifd"; public static string vendor = "astal"; public static string version = "0.1"; - private string cache_file; + private string state_file; + private string state_directory; 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 bool ignore_timeout { get; set; } - public bool dont_disturb { get; set; } + private bool _ignore_timeout; + public bool ignore_timeout { + get { return _ignore_timeout; } + set { + _ignore_timeout = value; + write_state(); + } + } + + private bool _dont_disturb; + public bool dont_disturb { + get { return _dont_disturb; } + set { + _dont_disturb = value; + write_state(); + } + } public signal void notified(uint id, bool replaced); public signal void resolved(uint id, ClosedReason reason); public signal void action_invoked(uint id, string action); + public signal void prop_changed(string prop); // emitting an event from proxy doesn't seem to work public void emit_resolved(uint id, ClosedReason reason) { resolved(id, reason); } @@ -32,24 +41,31 @@ internal class Daemon : Object { construct { cache_directory = Environment.get_user_cache_dir() + "/astal/notifd"; - cache_file = cache_directory + "/notifications.json"; + state_directory = Environment.get_user_state_dir() + "/astal/notifd"; + state_file = state_directory + "/notifications.json"; - if (FileUtils.test(cache_file, FileTest.EXISTS)) { + if (FileUtils.test(state_file, FileTest.EXISTS)) { try { uint8[] json; - File.new_for_path(cache_file).load_contents(null, out json, null); - var parser = new Json.Parser(); - parser.load_from_data((string)json); - var list = parser.get_root().get_array(); + File.new_for_path(state_file).load_contents(null, out json, null); + + var obj = Json.from_string((string)json); + + var list = obj.get_object().get_array_member("notifications"); for (var i = 0; i < list.get_length(); ++i) { add_notification(new Notification.from_json(list.get_object_element(i))); } n_id = list.get_length() + 1; + + _dont_disturb = obj.get_object().get_boolean_member("dont_disturb"); + _ignore_timeout = obj.get_object().get_boolean_member("ignore_timeout"); } catch (Error err) { warning("failed to load cache: %s", err.message); } } + notify.connect((prop) => prop_changed(prop.name)); + notified.connect(() => { notify_property("notifications"); }); @@ -57,7 +73,7 @@ internal class Daemon : Object { resolved.connect((id, reason) => { notifs.get(id).resolved(reason); notifs.remove(id); - cache(); + write_state(); notify_property("notifications"); notification_closed(id, reason); }); @@ -123,7 +139,7 @@ internal class Daemon : Object { notified(id, replaced); - cache(); + write_state(); return id; } @@ -135,21 +151,25 @@ internal class Daemon : Object { return replaced; } - private void cache() { + private void write_state() { var list = new Json.Builder().begin_array(); foreach (var n in notifications) { list.add_value(n.to_json()); } list.end_array(); - var generator = new Json.Generator(); - generator.set_root(list.get_root()); - var json = generator.to_data(null); + + var obj = new Json.Builder() + .begin_object() + .set_member_name("notifications").add_value(list.get_root()) + .set_member_name("ignore_timeout").add_boolean_value(ignore_timeout) + .set_member_name("dont_disturb").add_boolean_value(dont_disturb) + .end_object(); try { - if (!FileUtils.test(cache_directory, FileTest.EXISTS)) - File.new_for_path(cache_directory).make_directory_with_parents(null); + if (!FileUtils.test(state_directory, FileTest.EXISTS)) + File.new_for_path(state_directory).make_directory_with_parents(null); - FileUtils.set_contents_full(cache_file, json); + FileUtils.set_contents_full(state_file, Json.to_string(obj.get_root(), false)); } catch (Error err) { warning("failed to cache notifications: %s", err.message); } @@ -201,6 +221,9 @@ internal class Daemon : Object { var file_name = cache_directory + "/" + data.hash().to_string("%u.png"); try { + if (!FileUtils.test(cache_directory, FileTest.EXISTS)) + File.new_for_path(cache_directory).make_directory_with_parents(null); + var output_stream = File.new_for_path(file_name) .replace(null, false, FileCreateFlags.NONE, null); @@ -214,8 +237,7 @@ internal class Daemon : Object { return file_name; } - [DBus (visible = false)] - public Daemon register(DBusConnection conn) { + internal Daemon register(DBusConnection conn) { try { conn.register_object("/org/freedesktop/Notifications", this); } catch (Error err) { @@ -224,4 +246,10 @@ internal class Daemon : Object { return this; } } + +public enum AstalNotifd.ClosedReason { + EXPIRED = 1, + DISMISSED_BY_USER = 2, + CLOSED = 3, + UNDEFINED = 4, } diff --git a/src/notifd.vala b/src/notifd.vala index bb027f6..c962862 100644 --- a/src/notifd.vala +++ b/src/notifd.vala @@ -1,9 +1,10 @@ namespace AstalNotifd { -public Notifd get_default() { - return Notifd.get_default(); + public Notifd get_default() { + return Notifd.get_default(); + } } -public class Notifd : Object { +public class AstalNotifd.Notifd : Object { private static Notifd _instance; public static Notifd get_default() { if (_instance == null) @@ -62,7 +63,13 @@ public class Notifd : Object { construct { // hack to make it synchronous - var loop = new MainLoop(); + MainLoop? loop = null; + + if (!MainContext.default().is_owner()) { + loop = new MainLoop(); + } + + bool done = false; Bus.own_name( BusType.SESSION, @@ -74,22 +81,23 @@ public class Notifd : Object { ); active.connect(() => { - if (loop.is_running()) + done = true; + if (loop != null && loop.is_running()) { loop.quit(); + } }); - loop.run(); + if (loop != null) { + loop.run(); + } else { + while (!done) { + MainContext.default().iteration(false); + } + } } private void acquire_daemon(DBusConnection conn) { daemon = new Daemon().register(conn); - daemon.notified.connect((id, replaced) => notified(id, replaced)); - daemon.resolved.connect((id, reason) => resolved(id, reason)); - daemon.notify.connect((prop) => { - if (get_class().find_property(prop.name) != null) { - notify_property(prop.name); - } - }); } private void on_daemon_acquired() { @@ -97,6 +105,13 @@ public class Notifd : Object { proxy.stop(); proxy = null; } + daemon.notified.connect((id, replaced) => notified(id, replaced)); + daemon.resolved.connect((id, reason) => resolved(id, reason)); + daemon.notify.connect((prop) => { + if (get_class().find_property(prop.name) != null) { + notify_property(prop.name); + } + }); active(ActiveType.DAEMON); } @@ -119,8 +134,7 @@ public class Notifd : Object { } } -public enum ActiveType { +public enum AstalNotifd.ActiveType { DAEMON, PROXY, } -} diff --git a/src/notification.vala b/src/notification.vala index 55d62d1..0b4af06 100644 --- a/src/notification.vala +++ b/src/notification.vala @@ -1,21 +1,16 @@ -namespace AstalNotifd { -public enum Urgency { +public enum AstalNotifd.Urgency { LOW = 0, NORMAL = 1, CRITICAL = 2, } -public class Action { - public Action(string id, string label) { - this.id = id; - this.label = label; - } +public struct AstalNotifd.Action { public string id; public string label; } -public class Notification : Object { - private List<Action> _actions; +public class AstalNotifd.Notification : Object { + private List<Action?> _actions; private HashTable<string, Variant> hints; public int64 time { construct set; get; } @@ -25,7 +20,7 @@ public class Notification : Object { public string body { construct set; get; } public uint id { construct set; get; } public int expire_timeout { construct set; get; } - public List<Action> actions { get { return _actions; } } + public List<Action?> actions { get { return _actions; } } public string image { get { return get_str_hint("image-path"); } } public bool action_icons { get { return get_bool_hint("action-icons"); } } @@ -61,9 +56,12 @@ public class Notification : Object { ); this.hints = hints; - _actions = new List<Action>(); + _actions = new List<Action?>(); for (var i = 0; i < actions.length; i += 2) { - _actions.append(new Action(actions[i], actions[i + 1])); + _actions.append(Action() { + id = actions[i], + label = actions[i + 1] + }); } } @@ -109,13 +107,13 @@ public class Notification : Object { } break; case "actions": - _actions = new List<Action>(); + _actions = new List<Action?>(); for (var i = 0; i < node.get_array().get_length(); ++i) { var o = node.get_array().get_object_element(i); - _actions.append(new Action( - o.get_member("id").get_string(), - o.get_member("label").get_string() - )); + _actions.append(Action() { + id = o.get_member("id").get_string(), + label = o.get_member("label").get_string() + }); } break; default: break; @@ -160,4 +158,3 @@ public class Notification : Object { .get_root(); } } -} diff --git a/src/proxy.vala b/src/proxy.vala index f32abbd..bedb8b9 100644 --- a/src/proxy.vala +++ b/src/proxy.vala @@ -1,6 +1,5 @@ -namespace AstalNotifd { [DBus (name = "org.freedesktop.Notifications")] -internal interface IDaemon : Object { +internal interface AstalNotifd.IDaemon : DBusProxy { public abstract bool ignore_timeout { get; set; } public abstract bool dont_disturb { get; set; } @@ -9,12 +8,13 @@ internal interface IDaemon : Object { public signal void notified(uint id, bool replaced); public signal void resolved(uint id, ClosedReason reason); + public signal void prop_changed(string prop); public abstract void emit_resolved(uint id, ClosedReason reason); public abstract void emit_action_invoked(uint id, string action); } -internal class DaemonProxy : Object { +internal class AstalNotifd.DaemonProxy : Object { private HashTable<uint, Notification> notifs = new HashTable<uint, Notification>((i) => i, (a, b) => a == b); @@ -43,8 +43,8 @@ internal class DaemonProxy : Object { public signal void notified(uint id, bool replaced); public signal void resolved(uint id, ClosedReason reason); - IDaemon proxy; - List<ulong> ids = new List<ulong>(); + private IDaemon proxy; + private List<ulong> ids = new List<ulong>(); public void stop() { if (ids.length() > 0) { @@ -72,8 +72,8 @@ internal class DaemonProxy : Object { var version = variant.get_child_value(2).get_string(); var running = name == Daemon.name - && vendor == Daemon.vendor - && version == Daemon.version; + && vendor == Daemon.vendor + && version == Daemon.version; if (running) { setup_proxy(); @@ -97,19 +97,21 @@ internal class DaemonProxy : Object { 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.prop_changed.connect((prop) => { + if (prop == "ignore-timeout" || prop == "dont-disturb") + notify_property(prop); })); ids.append(proxy.notified.connect((id, replaced) => { add_notification(id); notified(id, replaced); + notify_property("notifications"); })); ids.append(proxy.resolved.connect((id, reason) => { notifs.remove(id); resolved(id, reason); + notify_property("notifications"); })); } @@ -125,4 +127,3 @@ internal class DaemonProxy : Object { } } } -} |