diff options
author | Aylur <[email protected]> | 2024-05-21 12:14:29 +0200 |
---|---|---|
committer | Aylur <[email protected]> | 2024-05-21 12:14:29 +0200 |
commit | 76036a1adbd29235620020f524d0709f3cefe168 (patch) | |
tree | d851b1b4fb40bf67cb04c0cacff39a38c8074987 |
init 0.1.0
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | flake.lock | 27 | ||||
-rw-r--r-- | flake.nix | 41 | ||||
-rw-r--r-- | meson-install.sh | 16 | ||||
-rw-r--r-- | meson.build | 14 | ||||
-rw-r--r-- | meson_options.txt | 2 | ||||
-rw-r--r-- | src/cli.vala.in | 47 | ||||
-rw-r--r-- | src/daemon.vala | 220 | ||||
-rw-r--r-- | src/meson.build | 76 | ||||
-rw-r--r-- | src/notifd.vala | 146 | ||||
-rw-r--r-- | src/notification.vala | 163 | ||||
-rw-r--r-- | src/proxy.vala | 97 | ||||
-rw-r--r-- | version | 1 |
14 files changed, 866 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f047207 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +result +.cache/ +test.sh +tmp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..352f6c1 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# notifd + +a notification daemon + +## TODO + +- docs +- cli options for + - [ ] getting a list of every notification + - [ ] getting a notification by its id + - [ ] invoke notification action by id diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0c0646b --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1715961556, + "narHash": "sha256-+NpbZRCRisUHKQJZF3CT+xn14ZZQO+KjxIIanH3Pvn4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4a6b83b05df1a8bd7d99095ec4b4d271f2956b64", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..5389e8a --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs }: + let + version = builtins.replaceStrings ["\n"] [""] (builtins.readFile ./version); + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + + nativeBuildInputs = with pkgs; [ + gobject-introspection + meson + pkg-config + ninja + vala + ]; + + buildInputs = with pkgs; [ + glib + gdk-pixbuf + json-glib + ]; + in { + packages.${system} = rec { + default = notifd; + notifd = pkgs.stdenv.mkDerivation { + inherit nativeBuildInputs buildInputs; + pname = "notifd"; + version = version; + src = ./.; + # outputs = ["out" "dev"]; + }; + }; + + devShells.${system} = { + default = pkgs.mkShell { + inherit nativeBuildInputs buildInputs; + }; + }; + }; +} diff --git a/meson-install.sh b/meson-install.sh new file mode 100644 index 0000000..0e6b258 --- /dev/null +++ b/meson-install.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +meson setup \ + --prefix /usr \ + --libexecdir lib \ + --sbindir bin \ + --buildtype plain \ + --auto-features enabled \ + --wrap-mode nodownload \ + -D b_lto=false \ + -D b_pie=true \ + -D python.bytecompile=1 \ + --wipe \ + build + +meson install -C build diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..e85fbc5 --- /dev/null +++ b/meson.build @@ -0,0 +1,14 @@ +project( + 'notifd', + 'vala', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), + meson_version: '>= 0.62.0', + default_options: [ + 'warning_level=2', + 'werror=false', + 'c_std=gnu11', + ], +) + +subdir('src') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..0966cdf --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,2 @@ +option('typelib', type: 'boolean', value: true, description: 'Needed files for runtime bindings') +option('cli_client', type: 'boolean', value: true, description: 'cli client for notifd') diff --git a/src/cli.vala.in b/src/cli.vala.in new file mode 100644 index 0000000..056a69d --- /dev/null +++ b/src/cli.vala.in @@ -0,0 +1,47 @@ +private static bool version; +private static bool help; + +private const OptionEntry[] options = { + { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, + { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, + { null }, +}; + +int main(string[] argv) { + try { + var opts = new OptionContext(); + opts.add_main_entries(options, null); + opts.set_help_enabled(false); + opts.set_ignore_unknown_options(false); + opts.parse(ref argv); + } catch (OptionError err) { + printerr (err.message); + return 1; + } + + if (help) { + print("Cli client for astal-notifd\n\n"); + print("Usage:\n"); + print(" %s [flags] message\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"); + return 0; + } + + 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(); + return 0; +} diff --git a/src/daemon.vala b/src/daemon.vala new file mode 100644 index 0000000..fd344e7 --- /dev/null +++ b/src/daemon.vala @@ -0,0 +1,220 @@ +namespace AstalNotifd { +public enum ClosedReason { + EXPIRED = 1, + DISMISSED_BY_USER = 2, + CLOSED = 3, + UNDEFINED = 4, +} + +[DBus (name = "org.freedesktop.Notifications")] +internal class Daemon : Object { + public static string name = "notifd"; + public static string vendor = "astal"; + public static string version = "0.1"; + + private string cache_file; + 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); + + construct { + if (cache_directory == null) + cache_directory = Environment.get_user_cache_dir() + "/astal/notifd"; + + cache_file = cache_directory + "/notifications.json"; + + if (FileUtils.test(cache_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(); + 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); + } + } catch (Error err) { + warning("failed to load cache: %s", err.message); + } + } + + notified.connect(() => { + notify_property("notifications"); + }); + + resolved.connect((id, reason) => { + notifs.get(id).resolved(reason); + notifs.remove(id); + cache(); + notify_property("notifications"); + notification_closed(id, reason); + }); + } + + public uint[] notification_ids() throws DBusError, IOError { + var keys = notifs.get_keys(); + uint[] id = new uint[keys.length()]; + for (var i = 0; i < keys.length(); ++i) + id[i] = keys.nth_data(i); + return id; + } + + [DBus (visible = false)] + public List<weak Notification> notifications { + owned get { return notifs.get_values(); } + } + + [DBus (visible = false)] + public Notification get_notification(uint id) throws DBusError, IOError { + return notifs.get(id); + } + + public string get_notification_json(uint id) throws DBusError, IOError { + return notifs.get(id).to_json_string(); + } + + [DBus (name = "Notify")] + public uint Notify( + string app_name, + uint replaces_id, + string app_icon, + string summary, + string body, + string[] actions, + HashTable<string, Variant> hints, + int expire_timeout + ) throws DBusError, IOError { + if (hints.get("image-data") != null) { + var file = cache_image(hints.get("image-data"), app_name); + if (file != null) { + hints.set("image-path", new Variant.string(file)); + hints.remove("image-data"); + } + } + + // deprecated hints + hints.remove("image_data"); + 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)); + + if (!ignore_timeout && expire_timeout > 0) { + Timeout.add(expire_timeout, () => { + resolved(id, ClosedReason.EXPIRED); + return Source.REMOVE; + }, Priority.DEFAULT); + } + + notifs.set(id, n); + if (!dont_disturb) + notified(id); + + cache(); + return id; + } + + private void cache() { + 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); + + try { + if (!FileUtils.test(cache_directory, FileTest.EXISTS)) + File.new_for_path(cache_directory).make_directory_with_parents(null); + + FileUtils.set_contents_full(cache_file, json); + } catch (Error err) { + warning("failed to cache notifications: %s", err.message); + } + } + + 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 { + resolved(id, ClosedReason.CLOSED); + } + + public void get_server_information( + out string name, + out string vendor, + out string version, + out string spec_version + ) throws DBusError, IOError { + name = Daemon.name; + vendor = Daemon.vendor; + version = Daemon.version; + spec_version = "1.2"; + } + + public string[] get_capabilities() throws DBusError, IOError { + return {"action-icons", "actions", "body", "icon-static", "persistence", "sound"}; + } + + private string? cache_image(Variant image, string app_name) { + int w = image.get_child_value(0).get_int32(); + int h = image.get_child_value(1).get_int32(); + int rs = image.get_child_value(2).get_int32(); + bool alpha = image.get_child_value(3).get_boolean(); + int bps = image.get_child_value(4).get_int32(); + Bytes data = image.get_child_value(6).get_data_as_bytes(); + + if (bps != 8) { + warning("Can not cache image from %s. %s", app_name, + "Currently only RGB images with 8 bits per sample are supported."); + return null; + } + + var pixbuf = new Gdk.Pixbuf.from_bytes( + data, Gdk.Colorspace.RGB, alpha, bps, w, h, rs); + + if (pixbuf == null) + return null; + + var file_name = cache_directory + "/" + data.hash().to_string("%u.png"); + + try { + var output_stream = File.new_for_path(file_name) + .replace(null, false, FileCreateFlags.NONE, null); + + pixbuf.save_to_streamv(output_stream, "png", null, null, null); + output_stream.close(null); + } catch (Error err) { + warning("could not cache image %s", err.message); + return null; + } + + return file_name; + } + + [DBus (visible = false)] + public Daemon register(DBusConnection conn) { + try { + conn.register_object("/org/freedesktop/Notifications", this); + } catch (Error err) { + critical(err.message); + } + return this; + } +} +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..c1d8c5e --- /dev/null +++ b/src/meson.build @@ -0,0 +1,76 @@ +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +notifd_gir = 'astal_notifd-' + api_version + '.gir' +notifd_typelib = 'astal_notifd-' + api_version + '.typelib' +notifd_so = 'libastalnotifd.so.' + meson.project_version() + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), + dependency('gdk-pixbuf-2.0'), +] + +sources = files( + 'daemon.vala', + 'notifd.vala', + 'proxy.vala', + 'notification.vala', +) + +libnotifd = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '.vapi', + vala_gir: notifd_gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], +) + +import('pkgconfig').generate( + description: 'libastalnotifd', + libraries: libnotifd, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: 'gio-2.0', + install_dir: get_option('libdir') / 'pkgconfig', +) + +if get_option('typelib') + custom_target( + notifd_typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / notifd_gir, + ], + input: libnotifd, + output: notifd_typelib, + depends: libnotifd, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + +if get_option('cli_client') + cli = configure_file( + input: 'cli.vala.in', + output: 'cli.vala', + configuration: { + 'VERSION': meson.project_version(), + }, + ) + executable( + meson.project_name(), + [cli, sources], + dependencies: deps, + install: true, + ) +endif diff --git a/src/notifd.vala b/src/notifd.vala new file mode 100644 index 0000000..220278c --- /dev/null +++ b/src/notifd.vala @@ -0,0 +1,146 @@ +namespace AstalNotifd { +public enum ActiveType { + DAEMON, + PROXY, +} + +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; + } + set { + if (proxy != null) + proxy.ignore_timeout = value; + else + daemon.ignore_timeout = value; + } + } + + public bool dont_disturb { + get { + return proxy != null ? proxy.dont_disturb : daemon.dont_disturb; + } + set { + if (proxy != null) + proxy.dont_disturb = value; + else + daemon.dont_disturb = value; + } + } + + public List<weak Notification> notifications { + owned get { return notifs.get_values(); } + } + + public uint[] notification_ids() throws Error { + return proxy != null ? proxy.notification_ids() : daemon.notification_ids(); + } + + public Notification get_notification(uint id) { + return notifs.get(id); + } + + public string get_notification_json(uint id) { + return notifs.get(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); + } + } + + construct { + Bus.own_name( + BusType.SESSION, + "org.freedesktop.Notifications", + BusNameOwnerFlags.NONE, + try_daemon, + () => { + if (proxy != null) { + proxy.stop(); + proxy = null; + } + active(ActiveType.DAEMON); + }, + try_proxy + ); + } + + private void try_daemon(DBusConnection conn) { + daemon = new Daemon().register(conn); + daemon.notified.connect((id) => notified(id)); + daemon.resolved.connect((id, reason) => resolved(id, reason)); + daemon.notify.connect((prop) => { + if (get_class().find_property(prop.name) != null) { + 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; + } + + proxy.notified.connect((id) => notified(id)); + proxy.resolved.connect((id, reason) => resolved(id, reason)); + proxy.notify.connect((prop) => { + if (get_class().find_property(prop.name) != null) { + notify_property(prop.name); + } + }); + } +} +} diff --git a/src/notification.vala b/src/notification.vala new file mode 100644 index 0000000..bec014d --- /dev/null +++ b/src/notification.vala @@ -0,0 +1,163 @@ +namespace AstalNotifd { +public enum Urgency { + LOW = 0, + NORMAL = 1, + CRITICAL = 2, +} + +public class Action { + public Action(string id, string label) { + this.id = id; + this.label = label; + } + public string id; + public string label; +} + +public class Notification : Object { + private List<Action> _actions; + private HashTable<string, Variant> hints; + + public int64 time { construct set; get; } + public string app_name { construct set; get; } + public string app_icon { construct set; get; } + public string summary { construct set; get; } + 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 string image { get { return get_str_hint("image-path"); } } + public bool action_icons { get { return get_bool_hint("action-icons"); } } + public string category { get { return get_str_hint("category"); } } + public string desktop_entry { get { return get_str_hint("desktop-entry"); } } + public bool resident { get { return get_bool_hint("resident"); } } + public string sound_file { get { return get_str_hint("sound-file"); } } + public string sound_name { get { return get_str_hint("sound-name"); } } + public bool suppress_sound { get { return get_bool_hint("suppress-sound"); } } + public bool transient { get { return get_bool_hint("transient"); } } + public int x { get { return get_int_hint("x"); } } + public int y { get { return get_int_hint("y"); } } + public Urgency urgency { get { return get_int_hint("urgency"); } } + + internal Notification( + string app_name, + uint id, + string app_icon, + string summary, + string body, + string[] actions, + HashTable<string, Variant> hints, + int expire_timeout + ) { + Object( + app_name: app_name, + id: id, + app_icon: app_icon, + summary: summary, + body: body, + expire_timeout: expire_timeout, + time: new DateTime.now_local().to_unix() + ); + + this.hints = hints; + _actions = new List<Action>(); + for (var i = 0; i < actions.length; i += 2) { + _actions.append(new Action(actions[i], actions[i + 1])); + } + } + + public Variant? get_hint(string hint) { + return hints.contains(hint) ? hints.get(hint) : null; + } + + public unowned string get_str_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_string() : null; + } + + public bool get_bool_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_boolean() : false; + } + + public int get_int_hint(string hint) { + return hints.contains(hint) ? hints.get(hint).get_int32() : 0; + } + + public signal void resolved(ClosedReason reason); + public signal void dismissed(); + public signal void invoked(string action); + + public void dismiss() { dismissed(); } + public void invoke(string action) { invoked(action); } + + public Notification.from_json(Json.Object root) throws GLib.Error { + foreach (var key in root.get_members()) { + var node = root.get_member(key); + switch (key) { + case "id": id = (uint)node.get_int(); break; + case "time": time = node.get_int(); break; + case "expire_timeout": expire_timeout = (int)node.get_int(); break; + case "app_name": app_name = node.get_string(); break; + case "app_icon": app_icon = node.get_string(); break; + case "summary": summary = node.get_string(); break; + case "body": body = node.get_string(); break; + case "hints": + hints = new HashTable<string, Variant>(str_hash, str_equal); + var obj = node.get_object(); + foreach (var hint in obj.get_members()) { + hints.set(hint, Json.gvariant_deserialize(obj.get_member(hint), null)); + } + break; + case "actions": + _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() + )); + } + break; + default: break; + } + } + } + + public 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()); + } + + public string to_json_string() { + var generator = new Json.Generator(); + generator.set_root(to_json()); + return generator.to_data(null); + } + + public Json.Node to_json() { + var acts = new Json.Builder().begin_array(); + foreach (var action in actions) { + acts.begin_object() + .set_member_name("id").add_string_value(action.id) + .set_member_name("label").add_string_value(action.label) + .end_object(); + } + acts.end_array(); + + return new Json.Builder() + .begin_object() + .set_member_name("id").add_int_value(id) + .set_member_name("time").add_int_value(time) + .set_member_name("expire_timeout").add_int_value(expire_timeout) + .set_member_name("app_name").add_string_value(app_name) + .set_member_name("app_icon").add_string_value(app_icon) + .set_member_name("summary").add_string_value(summary) + .set_member_name("body").add_string_value(body) + .set_member_name("actions").add_value(acts.get_root()) + .set_member_name("hints").add_value(Json.gvariant_serialize(hints)) + .end_object() + .get_root(); + } +} +} diff --git a/src/proxy.vala b/src/proxy.vala new file mode 100644 index 0000000..526cd13 --- /dev/null +++ b/src/proxy.vala @@ -0,0 +1,97 @@ +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; } + + public abstract uint[] notification_ids() throws DBusError, IOError; + public abstract string get_notification_json(uint id) throws DBusError, IOError; + + public signal void notified(uint id); + public signal void resolved(uint id, ClosedReason reason); +} + +internal class DaemonProxy : Object { + public string cache_directory { + owned get { return proxy.cache_directory; } + set { proxy.cache_directory = value; } + } + + public bool ignore_timeout { + get { return proxy.ignore_timeout; } + set { proxy.ignore_timeout = value; } + } + + public bool dont_disturb { + get { return proxy.dont_disturb; } + set { proxy.dont_disturb = value; } + } + + public uint[] notification_ids() throws DBusError, IOError { + return proxy.notification_ids(); + } + + public string get_notification_json(uint id) throws DBusError, IOError { + return proxy.get_notification_json(id); + } + + public signal void notified(uint id); + public signal void resolved(uint id, ClosedReason reason); + + IDaemon proxy; + List<ulong> ids = new List<ulong>(); + + public void stop() { + if (ids.length() > 0) { + foreach (var id in ids) + SignalHandler.disconnect(proxy, id); + } + } + + public bool start() { + try { + var bus = Bus.get_sync(BusType.SESSION, null); + var variant = bus.call_sync( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "GetServerInformation", + null, + null, + DBusCallFlags.NONE, + -1, + null); + + var name = variant.get_child_value(0).get_string(); + var vendor = variant.get_child_value(1).get_string(); + var version = variant.get_child_value(2).get_string(); + + var running = name == Daemon.name + && vendor == Daemon.vendor + && 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); + })); + return true; + } else { + critical("cannot get proxy: %s is already running", name); + } + } catch (Error err) { + critical("cannot get proxy: %s", err.message); + } + return false; + } +} +} @@ -0,0 +1 @@ +0.1.0 |