diff --git a/meson.build b/meson.build index 41b6299..a38ca49 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ executable( 'src/DBus.vala', 'src/FdoActionGroup.vala', 'src/Notification.vala', + 'src/PortalProxy.vala', 'src/Widgets/MaskedImage.vala', css_gresource, dependencies: [ diff --git a/src/Application.vala b/src/Application.vala index 165efb4..22a7ed4 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -31,6 +31,7 @@ public class Notifications.Application : Gtk.Application { protected override bool dbus_register (DBusConnection connection, string object_path) throws Error { try { new Notifications.Server (connection); + new Notifications.PortalProxy (connection); } catch (Error e) { Error.prefix_literal (out e, "Registering notification server failed: "); throw e; @@ -68,6 +69,17 @@ public class Notifications.Application : Gtk.Application { name_lost (); } ); + + Bus.own_name_on_connection ( + get_dbus_connection (), + "io.elementary.notifications.PortalProxy", + dbus_flags, + () => hold (), + (conn, name) => { + critical ("Could not acquire bus: %s", name); + name_lost (); + } + ); } public static void play_sound (string sound_name) { diff --git a/src/PortalProxy.vala b/src/PortalProxy.vala new file mode 100644 index 0000000..67d4980 --- /dev/null +++ b/src/PortalProxy.vala @@ -0,0 +1,106 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + +// TEST CALL FOR DSPY: +// 'io.elementary.mail.desktop' +// 'new-mail' +// {'title': <'New mail from John Doe'>, 'body': <'You have a new mail from John Doe. Click to read it.'>, 'priority': <'high'>} + +[DBus (name = "io.elementary.notifications.PortalProxy")] +public class Notifications.PortalProxy : Object { + private const string ID_FORMAT = "%s:%s"; + + public signal void action_invoked (string app_id, string id, string action_name, Variant[] parameters); + + public HashTable supported_options { get; construct; } + public uint version { get; default = 2; } + + [DBus (visible = false)] + public DBusConnection connection { private get; construct; } + + private uint server_id; + private Gee.Map bubbles; + + public PortalProxy (DBusConnection connection) { + Object (connection: connection); + } + + ~PortalProxy () { + connection.unregister_object (server_id); + } + + construct { + try { + server_id = connection.register_object ("/io/elementary/notifications/PortalProxy", this); + } catch (Error e) { + critical (e.message); + } + + supported_options = new HashTable (str_hash, str_equal); + bubbles = new Gee.HashMap (); + } + + public void add_notification (string app_id, string id, HashTable data) throws Error { + if (!("title" in data)) { + throw new DBusError.FAILED ("Can't show notification without title"); + } + + unowned var title = data["title"].get_string (); + + unowned string body = ""; + if ("body-markup" in data) { + body = data["body-markup"].get_string (); + } else if ("body" in data) { + body = data["body"].get_string (); + } + + var app_icon = app_id; + var hints = new HashTable (str_hash, str_equal); + + var notification = new Notification (app_id, app_icon, title, body, hints) { + buttons = new GenericArray (0) + }; + + if (!Application.settings.get_boolean ("do-not-disturb") || notification.priority == GLib.NotificationPriority.URGENT) { + var app_settings = new Settings.with_path ( + "io.elementary.notifications.applications", + Application.settings.path.concat ("applications", "/", notification.app_id, "/") + ); + + if (app_settings.get_boolean ("bubbles")) { + var full_id = ID_FORMAT.printf (app_id, id); + + if (bubbles.has_key (full_id) && bubbles[full_id] != null) { + bubbles[full_id].notification = notification; + } else { + bubbles[full_id] = new Bubble (notification); + bubbles[full_id].close_request.connect (() => { + bubbles[full_id] = null; + return Gdk.EVENT_PROPAGATE; + }); + } + + bubbles[full_id].present (); + } + + if (app_settings.get_boolean ("sounds")) { + var sound = notification.priority != URGENT ? "dialog-information" : "dialog-warning"; + + Application.play_sound (sound); + } + } + } + + public void remove_notification (string app_id, string id) throws Error { + var full_id = ID_FORMAT.printf (app_id, id); + + if (!bubbles.has_key (full_id)) { + throw new DBusError.FAILED ("Provided id %s not found", id); + } + + bubbles[full_id].close (); + } +}