From f351a2f478d6eeeab4685a374a793b6410a69580 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Mon, 12 Jun 2023 13:58:10 +0200 Subject: [PATCH 1/3] Squashed commit of the following: commit 46848f68650eb5d3a7e77ed1d28d6eb29c7b2fb0 Author: Gustavo Marques Date: Mon Jun 5 16:04:27 2023 -0300 Background: make asynchronous, get notifications proxy lazily make all of the dbus calls and methods asynchronous so that we don't end up blocking others interfaces, methods and initialization. the notification proxy is now also started when we first call send_notification() so we don't end starting it early than necessary or blocking the initialization for too much time. Signed-off-by: Gustavo Marques commit d1d2a43e4d0ec842cb9a1a1c7d383314c2ab6a55 Author: Gustavo Marques Date: Mon Jun 5 15:46:55 2023 -0300 Background: minor changes * move dbus interfaces out of the classes bodies, use constants for path and name. * use enumeration values where applicable and skip the namespace when possible. * simplify the response callback in the notify_background(). * make the notification_closed() callback return ALLOW_ONCE in more types of reasons. * remove temporary variables where applicable. Signed-off-by: Gustavo Marques --- src/Background/NotificationRequest.vala | 140 ++++++++++++++---------- src/Background/Portal.vala | 119 ++++++++++---------- 2 files changed, 142 insertions(+), 117 deletions(-) diff --git a/src/Background/NotificationRequest.vala b/src/Background/NotificationRequest.vala index 997e4bd6..71f0751c 100644 --- a/src/Background/NotificationRequest.vala +++ b/src/Background/NotificationRequest.vala @@ -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 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 notification_by_id; - private static Notifications notifications; + private static HashTable requests; + private static Fdo.Notifications? notifications; private uint32 id = 0; + [CCode (type_signature = "u")] public enum NotifyBackgroundResult { FORBID, ALLOW, @@ -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 hints, - int32 expire_timeout - ) throws DBusError, IOError; + static construct { + requests = new HashTable (null, null); } - [DBus (visible = false)] - public static void init (DBusConnection connection) { - notification_by_id = new HashTable (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 + notification.response (ALLOW_ONCE); } } [DBus (visible = false)] public void send_notification (string app_id, string app_name) { + if (notifications == null) { + Bus.get_proxy.begin (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 (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 } diff --git a/src/Background/Portal.vala b/src/Background/Portal.vala index 21e47f45..bd8cf483 100644 --- a/src/Background/Portal.vala +++ b/src/Background/Portal.vala @@ -3,58 +3,69 @@ * SPDX-License-Identifier: LGPL-2.1-or-later */ +[DBus (name = "org.pantheon.gala.DesktopIntegration")] +private interface Gala.DesktopIntegration : Object { + public signal void running_applications_changed (); + + public const string NAME = "org.pantheon.gala"; + public const string PATH = "/org/pantheon/gala/DesktopInterface"; + + public struct RunningApplications { + string app_id; + HashTable details; + } + + public abstract async RunningApplications[] get_running_applications () throws DBusError, IOError; +} + [DBus (name = "org.freedesktop.impl.portal.Background")] public class Background.Portal : Object { public signal void running_applications_changed (); + private Gala.DesktopIntegration? desktop_integration; private DBusConnection connection; - private DesktopIntegration desktop_integration; public Portal (DBusConnection connection) { this.connection = connection; - NotificationRequest.init (connection); - try { - desktop_integration = connection.get_proxy_sync ("org.pantheon.gala", "/org/pantheon/gala/DesktopInterface"); - desktop_integration.running_applications_changed.connect (() => running_applications_changed ()); - } catch { - warning ("Cannot connect to compositor, background portal working with reduced functionality."); - } - } - - [DBus (name = "org.pantheon.gala.DesktopIntegration")] - public interface DesktopIntegration : Object { - public struct RunningApplications { - string app_id; - HashTable details; - } - public signal void running_applications_changed (); - public abstract RunningApplications[] get_running_applications () throws DBusError, IOError; + connection.get_proxy.begin ( + Gala.DesktopIntegration.NAME, + Gala.DesktopIntegration.PATH, + NONE, null, (obj, res) => { + try { + desktop_integration = connection.get_proxy.end (res); + desktop_integration.running_applications_changed.connect (() => running_applications_changed ()); + } catch { + warning ("Cannot connect to compositor, portal working with reduced functionality."); + } + } + ); } + [CCode (type_signature = "u")] private enum ApplicationState { BACKGROUND, RUNNING, ACTIVE } - public HashTable get_app_state () throws DBusError, IOError { + public async HashTable get_app_state () throws DBusError, IOError { if (desktop_integration == null) { throw new DBusError.FAILED ("No connection to compositor."); } - var apps = desktop_integration.get_running_applications (); var results = new HashTable (null, null); - foreach (var app in apps) { + debug ("getting application states"); + + foreach (var app in yield desktop_integration.get_running_applications ()) { var app_id = app.app_id; if (app_id.has_suffix (".desktop")) { app_id = app_id.slice (0, app_id.last_index_of_char ('.')); } var app_state = ApplicationState.RUNNING; //FIXME: Don't hardcode: needs implementation on the gala side - - results[app_id] = (uint32) app_state; debug ("App state of '%s' set to %u (= %s).", app_id, app_state, app_state.to_string ()); + results[app_id] = app_state; } return results; @@ -69,40 +80,32 @@ public class Background.Portal : Object { ) throws DBusError, IOError { debug ("Notify background for '%s'.", app_id); - uint32 _response = 2; var _results = new HashTable (str_hash, str_equal); + uint32 _response = 2; - var notification_request = new NotificationRequest (); - - notification_request.response.connect ((result) => { - switch (result) { - case NotificationRequest.NotifyBackgroundResult.CANCELLED: - _response = 1; - break; - case NotificationRequest.NotifyBackgroundResult.FAILED: - break; - default: - _response = 0; - _results["result"] = (uint32) result; - break; + var notification = new NotificationRequest (); + notification.response.connect ((result) => { + if (result == CANCELLED) { + _response = 1; + } else if (result != FAILED) { + _response = 0; + _results["result"] = result; } debug ("Response to background activity of '%s': %s.", app_id, result.to_string ()); - notify_background.callback (); }); uint register_id = 0; try { - register_id = connection.register_object (handle, notification_request); + register_id = connection.register_object (handle, notification); } catch (Error e) { warning ("Failed to export request object: %s", e.message); throw new DBusError.OBJECT_PATH_IN_USE (e.message); } debug ("Sending desktop notification for '%s'.", app_id); - notification_request.send_notification (app_id, name); - + notification.send_notification (app_id, name); yield; connection.unregister_object (register_id); @@ -116,7 +119,7 @@ public class Background.Portal : Object { DBUS_ACTIVATABLE } - public bool enable_autostart ( + public async bool enable_autostart ( string app_id, bool enable, string[] commandline, @@ -135,13 +138,12 @@ public class Background.Portal : Object { */ var app_info = new DesktopAppInfo (_app_id + ".desktop"); if (app_info == null) { - _app_id = string.joinv ("-", commandline).replace ("--", "-"); + _app_id = string.joinv ("-", commandline).replace ("--", "-").replace ("--", "-"); } - var path = Path.build_filename (Environment.get_user_config_dir (), "autostart", _app_id + ".desktop"); - + var file = File.new_build_filename (Environment.get_user_config_dir (), "autostart", _app_id + ".desktop"); if (!enable) { - FileUtils.unlink (path); + try { yield file.delete_async (); } catch {} return false; } @@ -158,29 +160,32 @@ public class Background.Portal : Object { key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_COMMENT, string.joinv (" ", commandline)); } - key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_EXEC, flatpak_quote_argv (commandline)); - - if (flags == AutostartFlags.DBUS_ACTIVATABLE) { - key_file.set_boolean (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_DBUS_ACTIVATABLE, true); - } + key_file.set_string (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_EXEC, quote_argv (commandline)); + key_file.set_boolean (KeyFileDesktop.GROUP, KeyFileDesktop.KEY_DBUS_ACTIVATABLE, flags == DBUS_ACTIVATABLE); - if (app_id.strip () != "") { + if (app_id != "") { key_file.set_string (KeyFileDesktop.GROUP, "X-Flatpak", app_id); } + FileCreateFlags create_flags = PRIVATE | REPLACE_DESTINATION; + var contents = key_file.to_data (); + try { - key_file.save_to_file (path); + try { + yield file.replace_contents_async (contents.data, null, true, create_flags, null, null); + } catch (IOError.CANT_CREATE_BACKUP e) { + yield file.replace_contents_async (contents.data, null, false, create_flags, null, null); + } } catch (Error e) { warning ("Failed to write autostart file: %s", e.message); - return false; + throw new DBusError.FAILED (e.message); } - debug ("Autostart file installed for '%s'.", app_id); - + debug ("Autostart file installed at '%s'.", file.get_path ()); return true; } - private string flatpak_quote_argv (string[] argv) { + private string quote_argv (string[] argv) { var builder = new StringBuilder (); foreach (var arg in argv) { From 929f2b99d70c058eb735998f42ffd291dc4d3c62 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Mon, 12 Jun 2023 14:05:19 +0200 Subject: [PATCH 2/3] Make send_notification async and throw an error --- src/Background/NotificationRequest.vala | 38 +++++++++---------------- src/Background/Portal.vala | 8 ++++-- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/Background/NotificationRequest.vala b/src/Background/NotificationRequest.vala index 71f0751c..3a906601 100644 --- a/src/Background/NotificationRequest.vala +++ b/src/Background/NotificationRequest.vala @@ -84,21 +84,17 @@ public class Background.NotificationRequest : Object { } [DBus (visible = false)] - public void send_notification (string app_id, string app_name) { + public async void send_notification (string app_id, string app_name) throws Error { if (notifications == null) { - Bus.get_proxy.begin (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; + notifications = yield Bus.get_proxy ( + SESSION, + Fdo.Notifications.NAME, + Fdo.Notifications.PATH, + NONE, + null + ); + notifications.action_invoked.connect (action_invoked); + notifications.notification_closed.connect (notification_closed); } string[] actions = { @@ -112,22 +108,16 @@ public class Background.NotificationRequest : Object { hints["desktop-entry"] = app_id; hints["urgency"] = (uint8) 1; - notifications.notify.begin ( + yield notifications.notify ( 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); - } - } + -1 ); + + requests[id] = this; } public async void close () throws DBusError, IOError { diff --git a/src/Background/Portal.vala b/src/Background/Portal.vala index bd8cf483..ac7588ba 100644 --- a/src/Background/Portal.vala +++ b/src/Background/Portal.vala @@ -105,8 +105,12 @@ public class Background.Portal : Object { } debug ("Sending desktop notification for '%s'.", app_id); - notification.send_notification (app_id, name); - yield; + try { + yield notification.send_notification (app_id, name); + yield; + } catch (Error e) { + warning ("Failed to notify about background activity from '%s': %s", app_id, e.message); + } connection.unregister_object (register_id); response = _response; From 0dc009b3eb91dd1ffc56338f4ee93c52157cdcfe Mon Sep 17 00:00:00 2001 From: Leonhard Date: Mon, 12 Jun 2023 14:29:03 +0200 Subject: [PATCH 3/3] Fix issue --- src/Background/NotificationRequest.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Background/NotificationRequest.vala b/src/Background/NotificationRequest.vala index 3a906601..1959bd6c 100644 --- a/src/Background/NotificationRequest.vala +++ b/src/Background/NotificationRequest.vala @@ -108,7 +108,7 @@ public class Background.NotificationRequest : Object { hints["desktop-entry"] = app_id; hints["urgency"] = (uint8) 1; - yield notifications.notify ( + id = yield notifications.notify ( app_name, 0, "", _("Background activity"), _("“%s” is running in the background without appropriate permission").printf (app_name),