Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background: Make Asynchronous #84

Merged
merged 2 commits into from
Jun 12, 2023
Merged
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
140 changes: 80 additions & 60 deletions src/Background/NotificationRequest.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,48 @@
* SPDX-License-Identifier: LGPL-2.1-or-later
*/

[DBus (name = "org.freedesktop.Notifications")]
private interface Fdo.Notifications : Object {
public signal void action_invoked (uint32 id, string action_key);
public signal void notification_closed (uint32 id, CloseReason reason);

public const string NAME = "org.freedesktop.Notifications";
public const string PATH = "/org/freedesktop/Notifications";

[CCode (type_signature = "u")]
public enum CloseReason {
EXPIRED = 1,
DIMISSED,
CANCELLED
}

public async abstract void close_notification (uint32 id) throws DBusError, IOError;
public async abstract uint32 notify (
string app_name,
uint32 replaces_id,
string app_icon,
string summary,
string body,
string[] actions,
HashTable<string, Variant> hints,
int32 expire_timeout
) throws DBusError, IOError;
}

[DBus (name = "org.freedesktop.impl.portal.Request")]
public class NotificationRequest : Object {
public class Background.NotificationRequest : Object {
[DBus (visible = false)]
public signal void response (uint32 result);
public signal void response (NotifyBackgroundResult result);

private const string ACTION_ALLOW_BACKGROUND = "background.allow";
private const string ACTION_FORBID_BACKGROUND = "background.forbid";

private static HashTable<uint32, NotificationRequest> notification_by_id;
private static Notifications notifications;
private static HashTable<uint32, unowned NotificationRequest> requests;
private static Fdo.Notifications? notifications;

private uint32 id = 0;

[CCode (type_signature = "u")]
public enum NotifyBackgroundResult {
FORBID,
ALLOW,
Expand All @@ -24,95 +53,86 @@ public class NotificationRequest : Object {
FAILED
}

[DBus (name = "org.freedesktop.Notifications")]
public interface Notifications : Object {
public signal void action_invoked (uint32 id, string action_key);
public signal void notification_closed (uint32 id, uint32 reason);

public abstract void close_notification (uint32 id) throws DBusError, IOError;
public abstract uint32 notify (
string app_name,
uint32 replaces_id,
string app_icon,
string summary,
string body,
string[] actions,
HashTable<string, Variant> hints,
int32 expire_timeout
) throws DBusError, IOError;
static construct {
requests = new HashTable<uint32, unowned NotificationRequest> (null, null);
}

[DBus (visible = false)]
public static void init (DBusConnection connection) {
notification_by_id = new HashTable<uint32, NotificationRequest> (null, null);

try {
notifications = connection.get_proxy_sync ("org.freedesktop.Notifications", "/org/freedesktop/Notifications");
notifications.action_invoked.connect (on_action_invoked);
notifications.notification_closed.connect (on_notification_closed);
} catch {
warning ("Cannot connect to notifications dbus, background portal working with reduced functionality.");
}
}

private static void on_action_invoked (uint32 id, string action_key) {
var notification = notification_by_id.take (id);
private static void action_invoked (uint32 id, string action_key) {
unowned var notification = requests.take (id);
if (notification == null) {
return;
}

if (action_key == ACTION_ALLOW_BACKGROUND) {
notification.response (NotifyBackgroundResult.ALLOW);
notification.response (ALLOW);
} else {
notification.response (NotifyBackgroundResult.FORBID);
notification.response (FORBID);
}
}

private static void on_notification_closed (uint32 id, uint32 reason) {
var notification = notification_by_id.take (id);
private static void notification_closed (uint32 id, Fdo.Notifications.CloseReason reason) {
unowned var notification = requests.take (id);
if (notification == null) {
return;
}

if (reason == 2) { //Dismissed by user
notification.response (NotifyBackgroundResult.ALLOW_ONCE);
} else if (reason == 3) { //Closed via DBus call
notification.response (NotifyBackgroundResult.CANCELLED);
if (reason == CANCELLED) { // Closed via DBus call
notification.response (CANCELLED);
} else { // Dismissed, Expired, or something internal to the server
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A expired signal is also sent when the notification bubble closes. I think it's better to keep waiting then because the notification will stay in the indicator and can still send a response when something like elementary/wingpanel-indicator-notifications#258 gets merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This's a notification server bug, currently the server and indicator works at the back of each other, making the integration between both a mess. I'm working in a "rewrite/refactor" of the server right now, so that we can support the indicator properly.

we can wait till the server is fixed, but i don't see why, because the expiration would make the action activation useless in the server side. and we would need to fix that before something like elementary/wingpanel-indicator-notifications#258 is merged.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only out of curiosity: Why would the expiration make the action activation useless even if we ignore the expiration signal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the server emits a NotificationClosed signal it's mean that the notification doesn't exists anymore. so there will be no other signal emission for that notification id from the server.

notification.response (ALLOW_ONCE);
}
}

[DBus (visible = false)]
public void send_notification (string app_id, string app_name) {
if (notifications == null) {
Bus.get_proxy.begin<Fdo.Notifications> (SESSION, Fdo.Notifications.NAME, Fdo.Notifications.PATH, NONE, null,
(obj, res) => {
try {
notifications = Bus.get_proxy.end (res);
notifications.action_invoked.connect (action_invoked);
notifications.notification_closed.connect (notification_closed);
send_notification (app_id, app_name);
} catch {
warning ("Cannot connect to notifications server, skipping notify request for '%s'", app_id);
response (FAILED);
}
});
return;
}

string[] actions = {
ACTION_ALLOW_BACKGROUND,
_("Allow"),
ACTION_FORBID_BACKGROUND,
_("Forbid")
};

var hints = new HashTable<string, Variant> (null, null);
hints["desktop-entry"] = app_id;
hints["urgency"] = (uint8) 1;

try {
id = notifications.notify (
app_name, 0, "",
_("Background activity"),
_("“%s” is running in the background without appropriate permission").printf (app_name),
actions,
hints,
-1
);

notification_by_id.set (id, this);
} catch (Error e) {
warning ("Failed to send notification for app id '%s': %s", app_id, e.message);
response (NotifyBackgroundResult.FAILED);
}
notifications.notify.begin (
app_name, 0, "",
_("Background activity"),
_("“%s” is running in the background without appropriate permission").printf (app_name),
actions,
hints,
-1, (obj, res) => {
try {
id = notifications.notify.end (res);
requests[id] = this;
} catch (Error e) {
warning ("Failed to notify background activity from '%s': %s", app_id, e.message);
response (FAILED);
}
}
);
}

public void close () throws DBusError, IOError {
public async void close () throws DBusError, IOError {
try {
notifications.close_notification (id);
yield notifications.close_notification (id);
} catch (Error e) {
// the notification was already closed, or we lost the connection to the server
}
Expand Down
Loading