Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ executable(
'src/DBus.vala',
'src/FdoActionGroup.vala',
'src/Notification.vala',
'src/PortalProxy.vala',
'src/Widgets/MaskedImage.vala',
css_gresource,
dependencies: [
Expand Down
12 changes: 12 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
106 changes: 106 additions & 0 deletions src/PortalProxy.vala
Original file line number Diff line number Diff line change
@@ -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<string, Variant> 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<string, Bubble?> 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<string, Variant> (str_hash, str_equal);
bubbles = new Gee.HashMap<string, Bubble?> ();
}

public void add_notification (string app_id, string id, HashTable<string, Variant> 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<string, Variant> (str_hash, str_equal);

var notification = new Notification (app_id, app_icon, title, body, hints) {
buttons = new GenericArray<Notification.Button?> (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 ();
}
}