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) {