diff --git a/data/gtk.portal b/data/gtk.portal index 041cef4c..fcef5b86 100644 --- a/data/gtk.portal +++ b/data/gtk.portal @@ -1,4 +1,4 @@ [portal] DBusName=org.freedesktop.impl.portal.desktop.gtk -Interfaces=org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Lockdown; +Interfaces=org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Lockdown;org.freedesktop.impl.portal.Background; UseIn=gnome diff --git a/src/Makefile.am.inc b/src/Makefile.am.inc index ee468857..62d0c2fd 100644 --- a/src/Makefile.am.inc +++ b/src/Makefile.am.inc @@ -22,6 +22,7 @@ $(dbus_built_sources): src/Makefile.am.inc $(DESKTOP_PORTAL_INTERFACES_DIR)/org.freedesktop.impl.portal.ScreenCast.xml \ $(DESKTOP_PORTAL_INTERFACES_DIR)/org.freedesktop.impl.portal.RemoteDesktop.xml \ $(DESKTOP_PORTAL_INTERFACES_DIR)/org.freedesktop.impl.portal.Lockdown.xml \ + $(DESKTOP_PORTAL_INTERFACES_DIR)/org.freedesktop.impl.portal.Background.xml \ $(NULL) shell_built_sources = src/shell-dbus.c src/shell-dbus.h @@ -35,6 +36,7 @@ $(shell_built_sources): src/Makefile.am.inc $(top_srcdir)/data/org.gnome.Mutter.ScreenCast.xml \ $(top_srcdir)/data/org.gnome.Mutter.RemoteDesktop.xml \ $(top_srcdir)/data/org.gnome.Shell.Screenshot.xml \ + $(top_srcdir)/data/org.gnome.Shell.Introspect.xml \ $(top_srcdir)/data/org.gtk.Notifications.xml \ $(top_srcdir)/data/org.gnome.SessionManager.xml \ $(top_srcdir)/data/org.gnome.ScreenSaver.xml \ @@ -81,6 +83,8 @@ xdg_desktop_portal_gtk_SOURCES = \ src/appchooser.c \ src/notification.h \ src/notification.c \ + src/fdonotification.h \ + src/fdonotification.c \ src/inhibit.h \ src/inhibit.c \ src/appchooserrow.h \ @@ -121,6 +125,8 @@ xdg_desktop_portal_gtk_SOURCES = \ src/displaystatetracker.h \ src/lockdown.c \ src/lockdown.h \ + src/background.c \ + src/background.h \ $(NULL) nodist_xdg_desktop_portal_gtk_SOURCES = \ diff --git a/src/background.c b/src/background.c new file mode 100644 index 00000000..c4c7a1f4 --- /dev/null +++ b/src/background.c @@ -0,0 +1,449 @@ +#define _GNU_SOURCE 1 + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "xdg-desktop-portal-dbus.h" +#include "shell-dbus.h" + +#include "background.h" +#include "fdonotification.h" +#include "request.h" +#include "utils.h" + +static OrgGnomeShellIntrospect *shell; + +typedef enum { BACKGROUND, RUNNING, ACTIVE } AppState; + +static char * +get_actual_app_id (const char *app_id) +{ + g_autoptr(GDesktopAppInfo) info = g_desktop_app_info_new (app_id); + char *app = NULL; + + if (info) + app = g_desktop_app_info_get_string (info, "X-Flatpak"); + + g_debug ("looking up app id for %s: %s", app_id, app); + + return app; +} + +static gboolean +handle_get_app_state (XdpImplBackground *object, + GDBusMethodInvocation *invocation) +{ + g_autoptr(GVariant) windows = NULL; + g_autoptr(GHashTable) app_states = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GError) error = NULL; + GVariantBuilder builder; + GHashTableIter iter; + const char *key; + gpointer value; + + g_debug ("background: handle GetAppState"); + + if (!org_gnome_shell_introspect_call_get_windows_sync (shell, &windows, NULL, &error)) + { + g_dbus_method_invocation_return_error (invocation, + XDG_DESKTOP_PORTAL_ERROR, + XDG_DESKTOP_PORTAL_ERROR_FAILED, + "Could not get window list: %s", error->message); + return TRUE; + } + + if (windows) + { + g_autoptr(GVariantIter) iter = g_variant_iter_new (windows); + GVariant *dict; + + while (g_variant_iter_loop (iter, "{t@a{sv}}", NULL, &dict)) + { + const char *app_id = NULL; + char *app; + gboolean hidden = FALSE; + gboolean focus = FALSE; + AppState state = BACKGROUND; + + g_variant_lookup (dict, "app-id", "&s", &app_id); + g_variant_lookup (dict, "is-hidden", "b", &hidden); + g_variant_lookup (dict, "has-focus", "b", &focus); + + /* See https://gitlab.gnome.org/GNOME/gnome-shell/issues/1289 */ + app = get_actual_app_id (app_id); + if (app == NULL) + continue; + + state = GPOINTER_TO_INT (g_hash_table_lookup (app_states, app)); + if (!hidden) + state = MAX (state, RUNNING); + if (focus) + state = MAX (state, ACTIVE); + + g_hash_table_insert (app_states, app, GINT_TO_POINTER (state)); + } + } + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_hash_table_iter_init (&iter, app_states); + while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value)) + { + g_variant_builder_add (&builder, "{sv}", key, g_variant_new_uint32 (GPOINTER_TO_UINT (value))); + } + + xdp_impl_background_complete_get_app_state (object, + invocation, + g_variant_builder_end (&builder)); + + return TRUE; +} + +typedef enum { + FORBID = 0, + ALLOW = 1, + IGNORE = 2 +} NotifyResult; + +typedef struct { + XdpImplBackground *impl; + GDBusMethodInvocation *invocation; + Request *request; + char *id; + NotifyResult result; +} BackgroundHandle; + +static void +background_handle_free (gpointer data) +{ + BackgroundHandle *handle = data; + + g_object_unref (handle->request); + g_free (handle->id); + + g_free (handle); +} + +static void +background_handle_close (BackgroundHandle *handle) +{ + GDBusConnection *connection; + + connection = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (handle->impl)); + + fdo_remove_notification (connection, handle->request->app_id, handle->id); + background_handle_free (handle); +} + +static void +send_response (BackgroundHandle *handle) +{ + GVariantBuilder opt_builder; + int response = 0; + + g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&opt_builder, "{sv}", "result", g_variant_new_uint32 (handle->result)); + + if (handle->request->exported) + request_unexport (handle->request); + + xdp_impl_background_complete_notify_background (handle->impl, + handle->invocation, + response, + g_variant_builder_end (&opt_builder)); + + background_handle_close (handle); +} + +static void +activate_action (GDBusConnection *connection, + const char *app_id, + const char *id, + const char *name, + GVariant *parameter, + gpointer data) +{ + BackgroundHandle *handle = data; + + if (g_str_equal (name, "allow")) + { + g_debug ("Allow app %s to run in background", handle->request->app_id); + handle->result = ALLOW; + } + else if (g_str_equal (name, "forbid")) + { + g_debug ("Forbid app %s to run in background", handle->request->app_id); + handle->result = FORBID; + } + else if (g_str_equal (name, "ignore")) + { + g_debug ("Allow this instance of app %s to run in background", handle->request->app_id); + handle->result = IGNORE; + } + else + { + g_debug ("Unexpected action for app %s", handle->request->app_id); + handle->result = FORBID; + } + + send_response (handle); +} + +static gboolean +handle_close (XdpImplRequest *object, + GDBusMethodInvocation *invocation, + BackgroundHandle *handle) +{ + GVariantBuilder opt_builder; + + g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); + + xdp_impl_background_complete_notify_background (handle->impl, + handle->invocation, + 2, + g_variant_builder_end (&opt_builder)); + + if (handle->request->exported) + request_unexport (handle->request); + + background_handle_close (handle); + + xdp_impl_request_complete_close (object, invocation); + + return TRUE; +} + +static int count; + +static gboolean +handle_notify_background (XdpImplBackground *object, + GDBusMethodInvocation *invocation, + const char *arg_handle, + const char *arg_app_id, + const char *arg_name) +{ + g_autofree char *body = NULL; + GVariantBuilder builder; + GVariantBuilder bbuilder; + GVariantBuilder button; + GDBusConnection *connection; + const char *sender; + g_autoptr (Request) request = NULL; + BackgroundHandle *handle; + + g_debug ("background: handle NotifyBackground"); + + sender = g_dbus_method_invocation_get_sender (invocation); + request = request_new (sender, arg_app_id, arg_handle); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&builder, "{sv}", "title", g_variant_new_string (_("Background activity"))); + + body = g_strdup_printf (_("%s is running in the background."), arg_name); + g_variant_builder_add (&builder, "{sv}", "body", g_variant_new_string (body)); + + g_variant_builder_init (&bbuilder, G_VARIANT_TYPE ("aa{sv}")); + g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&button, "{sv}", "label", g_variant_new_string (_("Allow"))); + g_variant_builder_add (&button, "{sv}", "action", g_variant_new_string ("allow")); + g_variant_builder_add (&button, "{sv}", "target", g_variant_new_string (arg_app_id)); + + g_variant_builder_add (&bbuilder, "@a{sv}", g_variant_builder_end (&button)); + + g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&button, "{sv}", "label", g_variant_new_string (_("Forbid"))); + g_variant_builder_add (&button, "{sv}", "action", g_variant_new_string ("forbid")); + g_variant_builder_add (&button, "{sv}", "target", g_variant_new_string (arg_app_id)); + + g_variant_builder_add (&bbuilder, "@a{sv}", g_variant_builder_end (&button)); + + g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&button, "{sv}", "label", g_variant_new_string (_("Ignore"))); + g_variant_builder_add (&button, "{sv}", "action", g_variant_new_string ("ignore")); + g_variant_builder_add (&button, "{sv}", "target", g_variant_new_string (arg_app_id)); + + g_variant_builder_add (&bbuilder, "@a{sv}", g_variant_builder_end (&button)); + + g_variant_builder_add (&builder, "{sv}", "buttons", g_variant_builder_end (&bbuilder)); + + handle = g_new0 (BackgroundHandle, 1); + handle->impl = object; + handle->invocation = invocation; + handle->request = g_object_ref (request); + handle->id = g_strdup_printf ("notify_background_%d", count++); + + g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle); + + connection = g_dbus_method_invocation_get_connection (invocation); + + fdo_add_notification (connection, "", handle->id, g_variant_builder_end (&builder), activate_action, handle); + + request_export (request, connection); + + return TRUE; +} + +static gboolean +needs_quoting (const char *arg) +{ + while (*arg != 0) + { + char c = *arg; + if (!g_ascii_isalnum (c) && + !(c == '-' || c == '/' || c == '~' || + c == ':' || c == '.' || c == '_' || + c == '=' || c == '@')) + return TRUE; + arg++; + } + return FALSE; +} + +char * +flatpak_quote_argv (const char *argv[], + gssize len) +{ + GString *res = g_string_new (""); + int i; + + if (len == -1) + len = g_strv_length ((char **) argv); + + for (i = 0; i < len; i++) + { + if (i != 0) + g_string_append_c (res, ' '); + + if (needs_quoting (argv[i])) + { + g_autofree char *quoted = g_shell_quote (argv[i]); + g_string_append (res, quoted); + } + else + g_string_append (res, argv[i]); + } + + return g_string_free (res, FALSE); +} + +typedef enum { + AUTOSTART_FLAGS_NONE = 0, + AUTOSTART_FLAGS_ACTIVATABLE = 1 << 0, +} AutostartFlags; + +static gboolean +handle_enable_autostart (XdpImplBackground *object, + GDBusMethodInvocation *invocation, + const char *arg_app_id, + gboolean arg_enable, + const char * const *arg_commandline, + guint arg_flags) +{ + gboolean result = FALSE; + g_autofree char *dir = NULL; + g_autofree char *file = NULL; + g_autofree char *path = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *commandline = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + + g_debug ("background: handle EnableAutostart"); + + file = g_strconcat (arg_app_id, ".desktop", NULL); + dir = g_build_filename (g_get_user_config_dir (), "autostart", NULL); + path = g_build_filename (dir, file, NULL); + + if (!arg_enable) + { + unlink (path); + g_debug ("Removed %s", path); + goto out; + } + + if (g_mkdir_with_parents (dir, 0755) != 0) + { + g_warning ("Failed to create dirs %s", dir); + goto out; + } + + commandline = flatpak_quote_argv ((const char **)arg_commandline, -1); + + keyfile = g_key_file_new (); + + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_TYPE, + "Application"); + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_NAME, + arg_app_id); // FIXME + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_EXEC, + commandline); + if (arg_flags & AUTOSTART_FLAGS_ACTIVATABLE) + g_key_file_set_boolean (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, + TRUE); + g_key_file_set_string (keyfile, + G_KEY_FILE_DESKTOP_GROUP, + "X-Flatpak", + arg_app_id); + + if (!g_key_file_save_to_file (keyfile, path, &error)) + { + g_warning ("Failed to save %s: %s", path, error->message); + goto out; + } + + g_debug ("Wrote autostart file %s", path); + + result = TRUE; + +out: + xdp_impl_background_complete_enable_autostart (object, invocation, result); + + return TRUE; +} + +gboolean +background_init (GDBusConnection *bus, + GError **error) +{ + GDBusInterfaceSkeleton *helper; + + shell = org_gnome_shell_introspect_proxy_new_sync (bus, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + "org.gnome.Shell", + "/org/gnome/Shell/Introspect", + NULL, + NULL); + + helper = G_DBUS_INTERFACE_SKELETON (xdp_impl_background_skeleton_new ()); + + g_signal_connect (helper, "handle-get-app-state", G_CALLBACK (handle_get_app_state), NULL); + g_signal_connect (helper, "handle-notify-background", G_CALLBACK (handle_notify_background), NULL); + g_signal_connect (helper, "handle-enable-autostart", G_CALLBACK (handle_enable_autostart), NULL); + + if (!g_dbus_interface_skeleton_export (helper, + bus, + DESKTOP_PORTAL_OBJECT_PATH, + error)) + return FALSE; + + g_debug ("providing %s", g_dbus_interface_skeleton_get_info (helper)->name); + + return TRUE; +} diff --git a/src/background.h b/src/background.h new file mode 100644 index 00000000..f24f603c --- /dev/null +++ b/src/background.h @@ -0,0 +1,25 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#pragma once + +#include + +gboolean background_init (GDBusConnection *bus, GError **error); diff --git a/src/fdonotification.c b/src/fdonotification.c new file mode 100644 index 00000000..08ea9d38 --- /dev/null +++ b/src/fdonotification.c @@ -0,0 +1,433 @@ +#define _GNU_SOURCE 1 + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "xdg-desktop-portal-dbus.h" +#include "shell-dbus.h" + +#include "notification.h" +#include "fdonotification.h" +#include "request.h" +#include "utils.h" + +/* org.freedesktop.Notifications support. + * This code is adapted from the GFdoNotificationBackend in GIO. + */ + +static guint fdo_notify_subscription; +static GSList *fdo_notifications; + +typedef struct +{ + char *app_id; + char *id; + guint32 notify_id; + char *default_action; + GVariant *default_action_target; + ActivateAction activate_action; + gpointer data; +} FdoNotification; + +static void +fdo_notification_free (gpointer data) +{ + FdoNotification *n = data; + + g_free (n->app_id); + g_free (n->id); + g_free (n->default_action); + if (n->default_action_target) + g_variant_unref (n->default_action_target); + + g_slice_free (FdoNotification, n); +} + +FdoNotification * +fdo_find_notification (const char *app_id, + const char *id) +{ + GSList *l; + + for (l = fdo_notifications; l != NULL; l = l->next) + { + FdoNotification *n = l->data; + if (g_str_equal (n->app_id, app_id) && + g_str_equal (n->id, id)) + return n; + } + + return NULL; +} + +FdoNotification * +fdo_find_notification_by_notify_id (guint32 id) +{ + GSList *l; + + for (l = fdo_notifications; l != NULL; l = l->next) + { + FdoNotification *n = l->data; + if (n->notify_id == id) + return n; + } + + return NULL; +} + +static void +notify_signal (GDBusConnection *connection, + const char *sender_name, + const char *object_path, + const char *interface_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + guint32 id = 0; + const char *action = NULL; + FdoNotification *n; + + if (g_str_equal (signal_name, "NotificationClosed") && + g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) + { + g_variant_get (parameters, "(uu)", &id, NULL); + } + else if (g_str_equal (signal_name, "ActionInvoked") && + g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) + { + g_variant_get (parameters, "(u&s)", &id, &action); + } + else + return; + + n = fdo_find_notification_by_notify_id (id); + if (n == NULL) + return; + + if (action) + { + if (g_str_equal (action, "default")) + { + n->activate_action (connection, + n->app_id, + n->id, + n->default_action, + n->default_action_target, + n->data); + } + else + { + gchar *name; + GVariant *target; + + if (g_action_parse_detailed_name (action, &name, &target, NULL)) + { + n->activate_action (connection, + n->app_id, + n->id, + name, + target, + n->data); + g_free (name); + if (target) + g_variant_unref (target); + } + else + g_debug ("Could not parse action name %s", action); + } + } + + fdo_notifications = g_slist_remove (fdo_notifications, n); + fdo_notification_free (n); +} + +static guchar +urgency_from_priority (const char *priority) +{ + if (strcmp (priority, "low") == 0) + return 0; + else if (strcmp (priority, "normal") == 0) + return 1; + else + return 2; +} + +static void +notification_sent (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + FdoNotification *n = user_data; + GVariant *val; + GError *error = NULL; + static gboolean warning_printed = FALSE; + + val = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error); + if (val) + { + g_variant_get (val, "(u)", &n->notify_id); + g_variant_unref (val); + } + else + { + if (!warning_printed) + { + g_warning ("Unable to send notifications through org.freedesktop.Notifications: %s", + error->message); + warning_printed = TRUE; + } + + fdo_notifications = g_slist_remove (fdo_notifications, n); + fdo_notification_free (n); + + g_error_free (error); + } +} + +static void +call_notify (GDBusConnection *connection, + FdoNotification *fdo, + GVariant *notification) +{ + GVariantBuilder action_builder; + guint i; + GVariantBuilder hints_builder; + GVariant *icon; + const char *body; + const char *title; + g_autofree char *icon_name = NULL; + guchar urgency; + const char *dummy; + g_autoptr(GVariant) buttons = NULL; + const char *priority; + + if (fdo_notify_subscription == 0) + { + fdo_notify_subscription = + g_dbus_connection_signal_subscribe (connection, + "org.freedesktop.Notifications", + "org.freedesktop.Notifications", NULL, + "/org/freedesktop/Notifications", NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + notify_signal, NULL, NULL); + } + + g_variant_builder_init (&action_builder, G_VARIANT_TYPE_STRING_ARRAY); + if (g_variant_lookup (notification, "default-action", "&s", &dummy)) + { + g_variant_builder_add (&action_builder, "s", "default"); + g_variant_builder_add (&action_builder, "s", ""); + } + + buttons = g_variant_lookup_value (notification, "buttons", G_VARIANT_TYPE("aa{sv}")); + if (buttons) + for (i = 0; i < g_variant_n_children (buttons); i++) + { + g_autoptr(GVariant) button = NULL; + const char *label; + const char *action; + g_autoptr(GVariant) target = NULL; + g_autofree char *detailed_name = NULL; + + button = g_variant_get_child_value (buttons, i); + g_variant_lookup (button, "label", "&s", &label); + g_variant_lookup (button, "action", "&s", &action); + target = g_variant_lookup_value (button, "target", G_VARIANT_TYPE_VARIANT); + detailed_name = g_action_print_detailed_name (action, target); + + /* Actions named 'default' collide with libnotify's naming of the + * default action. Rewriting them to something unique is enough, + * because those actions can never be activated (they aren't + * prefixed with 'app.'). + */ + if (g_str_equal (detailed_name, "default")) + { + g_free (detailed_name); + detailed_name = g_dbus_generate_guid (); + } + + g_variant_builder_add_value (&action_builder, g_variant_new_string (detailed_name)); + g_variant_builder_add_value (&action_builder, g_variant_new_string (label)); + } + + g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry", g_variant_new_string (fdo->app_id)); + if (g_variant_lookup (notification, "priority", "&s", &priority)) + urgency = urgency_from_priority (priority); + else + urgency = 1; + g_variant_builder_add (&hints_builder, "{sv}", "urgency", g_variant_new_byte (urgency)); + + icon = g_variant_lookup_value (notification, "icon", NULL); + if (icon != NULL) + { + g_autoptr(GIcon) gicon = g_icon_deserialize (icon); + if (G_IS_FILE_ICON (gicon)) + { + GFile *file; + + file = g_file_icon_get_file (G_FILE_ICON (gicon)); + icon_name = g_file_get_path (file); + } + else if (G_IS_THEMED_ICON (gicon)) + { + const gchar* const* icon_names = g_themed_icon_get_names (G_THEMED_ICON (gicon)); + icon_name = g_strdup (icon_names[0]); + } + else if (G_IS_BYTES_ICON (gicon)) + { + g_autoptr(GInputStream) istream = NULL; + g_autoptr(GdkPixbuf) pixbuf = NULL; + int width, height, rowstride, n_channels, bits_per_sample; + GVariant *image; + gsize image_len; + + istream = g_loadable_icon_load (G_LOADABLE_ICON (gicon), + -1 /* unused */, + NULL /* type */, + NULL, + NULL); + pixbuf = gdk_pixbuf_new_from_stream (istream, NULL, NULL); + g_input_stream_close (istream, NULL, NULL); + + g_object_get (pixbuf, + "width", &width, + "height", &height, + "rowstride", &rowstride, + "n-channels", &n_channels, + "bits-per-sample", &bits_per_sample, + NULL); + + image_len = (height - 1) * rowstride + width * + ((n_channels * bits_per_sample + 7) / 8); + + image = g_variant_new ("(iiibii@ay)", + width, + height, + rowstride, + gdk_pixbuf_get_has_alpha (pixbuf), + bits_per_sample, + n_channels, + g_variant_new_from_data (G_VARIANT_TYPE ("ay"), + gdk_pixbuf_get_pixels (pixbuf), + image_len, + TRUE, + (GDestroyNotify) g_object_unref, + g_object_ref (pixbuf))); + g_variant_builder_add (&hints_builder, "{sv}", "image-data", image); + } + } + + if (icon_name == NULL) + icon_name = g_strdup (""); + + if (!g_variant_lookup (notification, "body", "&s", &body)) + body = ""; + if (!g_variant_lookup (notification, "title", "&s", &title)) + title= ""; + + g_dbus_connection_call (connection, + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "Notify", + g_variant_new ("(susssasa{sv}i)", + "", /* app name */ + fdo->notify_id, + icon_name, + title, + body, + &action_builder, + &hints_builder, + -1), /* expire_timeout */ + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, + notification_sent, fdo); +} + +static void +call_close (GDBusConnection *connection, + guint32 id) +{ + g_dbus_connection_call (connection, + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "CloseNotification", + g_variant_new ("(u)", id), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +gboolean +fdo_remove_notification (GDBusConnection *connection, + const char *app_id, + const char *id) +{ + FdoNotification *n; + + n = fdo_find_notification (app_id, id); + if (n) + { + if (n->notify_id > 0) + call_close (connection, n->notify_id); + + fdo_notifications = g_slist_remove (fdo_notifications, n); + fdo_notification_free (n); + + return TRUE; + } + + return FALSE; +} + +void +fdo_add_notification (GDBusConnection *connection, + const char *app_id, + const char *id, + GVariant *notification, + ActivateAction activate_action, + gpointer data) +{ + FdoNotification *n; + + n = fdo_find_notification (app_id, id); + if (n == NULL) + { + n = g_slice_new0 (FdoNotification); + n->app_id = g_strdup (app_id); + n->id = g_strdup (id); + n->notify_id = 0; + n->activate_action = activate_action; + n->data = data; + } + else + { + /* Only clear default action. All other fields are still valid */ + g_clear_pointer (&n->default_action, g_free); + g_clear_pointer (&n->default_action_target, g_variant_unref); + } + + g_variant_lookup (notification, "default-action", "s", &n->default_action); + n->default_action_target = g_variant_lookup_value (notification, "default-action-target", G_VARIANT_TYPE_VARIANT); + + fdo_notifications = g_slist_prepend (fdo_notifications, n); + + call_notify (connection, n, notification); +} + diff --git a/src/fdonotification.h b/src/fdonotification.h new file mode 100644 index 00000000..5bede28c --- /dev/null +++ b/src/fdonotification.h @@ -0,0 +1,41 @@ +/* + * Copyright © 2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Matthias Clasen + */ + +#pragma once + +#include + +typedef void (*ActivateAction) (GDBusConnection *connection, + const char *app_id, + const char *id, + const char *name, + GVariant *parameter, + gpointer data); + +void fdo_add_notification (GDBusConnection *connection, + const char *app_id, + const char *id, + GVariant *notification, + ActivateAction activate, + gpointer data); +gboolean fdo_remove_notification (GDBusConnection *connection, + const char *app_id, + const char *id); + diff --git a/src/notification.c b/src/notification.c index a5e11f71..1da8918c 100644 --- a/src/notification.c +++ b/src/notification.c @@ -19,6 +19,7 @@ #include "shell-dbus.h" #include "notification.h" +#include "fdonotification.h" #include "request.h" #include "utils.h" @@ -47,8 +48,6 @@ handle_add_notification_gtk (XdpImplNotification *object, const char *arg_id, GVariant *arg_notification) { - g_debug ("handle add-notification from %s using the gtk implementation", arg_app_id); - if (gtk_notifications) org_gtk_notifications_call_add_notification (gtk_notifications, arg_app_id, @@ -58,6 +57,8 @@ handle_add_notification_gtk (XdpImplNotification *object, notification_added, NULL); + g_debug ("handle add-notification from %s using the gtk implementation", arg_app_id); + xdp_impl_notification_complete_add_notification (object, invocation); } @@ -67,8 +68,6 @@ handle_remove_notification_gtk (XdpImplNotification *object, const char *arg_app_id, const char *arg_id) { - g_debug ("handle remove-notification from %s using the gtk implementation", arg_app_id); - if (gtk_notifications) org_gtk_notifications_call_remove_notification (gtk_notifications, arg_app_id, @@ -77,69 +76,9 @@ handle_remove_notification_gtk (XdpImplNotification *object, NULL, NULL); - xdp_impl_notification_complete_remove_notification (object, invocation); -} - -/* org.freedesktop.Notifications support. - * This code is adapted from the GFdoNotificationBackend in GIO. - */ - -static guint fdo_notify_subscription; -static GSList *fdo_notifications; - -typedef struct -{ - char *app_id; - char *id; - guint32 notify_id; - char *default_action; - GVariant *default_action_target; -} FdoNotification; - -static void -fdo_notification_free (gpointer data) -{ - FdoNotification *n = data; - - g_free (n->app_id); - g_free (n->id); - g_free (n->default_action); - if (n->default_action_target) - g_variant_unref (n->default_action_target); - - g_slice_free (FdoNotification, n); -} - -static FdoNotification * -fdo_find_notification (const char *app_id, - const char *id) -{ - GSList *l; - - for (l = fdo_notifications; l != NULL; l = l->next) - { - FdoNotification *n = l->data; - if (g_str_equal (n->app_id, app_id) && - g_str_equal (n->id, id)) - return n; - } - - return NULL; -} - -static FdoNotification * -fdo_find_notification_by_notify_id (guint32 id) -{ - GSList *l; - - for (l = fdo_notifications; l != NULL; l = l->next) - { - FdoNotification *n = l->data; - if (n->notify_id == id) - return n; - } + g_debug ("handle remove-notification from %s using the gtk implementation", arg_app_id); - return NULL; + xdp_impl_notification_complete_remove_notification (object, invocation); } static char * @@ -165,7 +104,8 @@ activate_action (GDBusConnection *connection, const char *app_id, const char *id, const char *name, - GVariant *parameter) + GVariant *parameter, + gpointer data) { g_autofree char *object_path = NULL; GVariantBuilder pdata; @@ -220,266 +160,6 @@ activate_action (GDBusConnection *connection, } } -static void -notify_signal (GDBusConnection *connection, - const char *sender_name, - const char *object_path, - const char *interface_name, - const char *signal_name, - GVariant *parameters, - gpointer user_data) -{ - guint32 id = 0; - const char *action = NULL; - FdoNotification *n; - - if (g_str_equal (signal_name, "NotificationClosed") && - g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) - { - g_variant_get (parameters, "(uu)", &id, NULL); - } - else if (g_str_equal (signal_name, "ActionInvoked") && - g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) - { - g_variant_get (parameters, "(u&s)", &id, &action); - } - else - return; - - n = fdo_find_notification_by_notify_id (id); - if (n == NULL) - return; - - if (action) - { - if (g_str_equal (action, "default")) - { - activate_action (connection, - n->app_id, - n->id, - n->default_action, - n->default_action_target); - } - else - { - gchar *name; - GVariant *target; - - if (g_action_parse_detailed_name (action, &name, &target, NULL)) - { - activate_action (connection, - n->app_id, - n->id, - name, - target); - g_free (name); - if (target) - g_variant_unref (target); - } - } - } - - fdo_notifications = g_slist_remove (fdo_notifications, n); - fdo_notification_free (n); -} - -static guchar -urgency_from_priority (const char *priority) -{ - if (strcmp (priority, "low") == 0) - return 0; - else if (strcmp (priority, "normal") == 0) - return 1; - else - return 2; -} - -static void -call_notify (GDBusConnection *connection, - const char *app_id, - guint32 replace_id, - GVariant *notification, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GVariantBuilder action_builder; - guint i; - GVariantBuilder hints_builder; - GVariant *icon; - const char *body; - const char *title; - g_autofree char *icon_name = NULL; - guchar urgency; - const char *dummy; - g_autoptr(GVariant) buttons = NULL; - const char *priority; - - g_variant_builder_init (&action_builder, G_VARIANT_TYPE_STRING_ARRAY); - if (g_variant_lookup (notification, "default-action", "&s", &dummy)) - { - g_variant_builder_add (&action_builder, "s", "default"); - g_variant_builder_add (&action_builder, "s", ""); - } - - buttons = g_variant_lookup_value (notification, "buttons", G_VARIANT_TYPE("aa{sv}")); - if (buttons) - for (i = 0; i < g_variant_n_children (buttons); i++) - { - g_autoptr(GVariant) button = NULL; - const char *label; - const char *action; - g_autoptr(GVariant) target = NULL; - g_autofree char *detailed_name = NULL; - - button = g_variant_get_child_value (buttons, i); - g_variant_lookup (button, "label", "&s", &label); - g_variant_lookup (button, "action", "&s", &action); - target = g_variant_lookup_value (button, "target", G_VARIANT_TYPE_VARIANT); - detailed_name = g_action_print_detailed_name (action, target); - - /* Actions named 'default' collide with libnotify's naming of the - * default action. Rewriting them to something unique is enough, - * because those actions can never be activated (they aren't - * prefixed with 'app.'). - */ - if (g_str_equal (detailed_name, "default")) - { - g_free (detailed_name); - detailed_name = g_dbus_generate_guid (); - } - - g_variant_builder_add_value (&action_builder, g_variant_new_string (detailed_name)); - g_variant_builder_add_value (&action_builder, g_variant_new_string (label)); - } - - g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}")); - g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry", g_variant_new_string (app_id)); - if (g_variant_lookup (notification, "priority", "&s", &priority)) - urgency = urgency_from_priority (priority); - else - urgency = 1; - g_variant_builder_add (&hints_builder, "{sv}", "urgency", g_variant_new_byte (urgency)); - - icon = g_variant_lookup_value (notification, "icon", NULL); - if (icon != NULL) - { - g_autoptr(GIcon) gicon = g_icon_deserialize (icon); - if (G_IS_FILE_ICON (gicon)) - { - GFile *file; - - file = g_file_icon_get_file (G_FILE_ICON (gicon)); - icon_name = g_file_get_path (file); - } - else if (G_IS_THEMED_ICON (gicon)) - { - const gchar* const* icon_names = g_themed_icon_get_names (G_THEMED_ICON (gicon)); - icon_name = g_strdup (icon_names[0]); - } - else if (G_IS_BYTES_ICON (gicon)) - { - g_autoptr(GInputStream) istream = NULL; - g_autoptr(GdkPixbuf) pixbuf = NULL; - int width, height, rowstride, n_channels, bits_per_sample; - GVariant *image; - gsize image_len; - - istream = g_loadable_icon_load (G_LOADABLE_ICON (gicon), - -1 /* unused */, - NULL /* type */, - NULL, - NULL); - pixbuf = gdk_pixbuf_new_from_stream (istream, NULL, NULL); - g_input_stream_close (istream, NULL, NULL); - - g_object_get (pixbuf, - "width", &width, - "height", &height, - "rowstride", &rowstride, - "n-channels", &n_channels, - "bits-per-sample", &bits_per_sample, - NULL); - - image_len = (height - 1) * rowstride + width * - ((n_channels * bits_per_sample + 7) / 8); - - image = g_variant_new ("(iiibii@ay)", - width, - height, - rowstride, - gdk_pixbuf_get_has_alpha (pixbuf), - bits_per_sample, - n_channels, - g_variant_new_from_data (G_VARIANT_TYPE ("ay"), - gdk_pixbuf_get_pixels (pixbuf), - image_len, - TRUE, - (GDestroyNotify) g_object_unref, - g_object_ref (pixbuf))); - g_variant_builder_add (&hints_builder, "{sv}", "image-data", image); - } - } - - if (icon_name == NULL) - icon_name = g_strdup (""); - - if (!g_variant_lookup (notification, "body", "&s", &body)) - body = ""; - if (!g_variant_lookup (notification, "title", "&s", &title)) - title= ""; - - g_dbus_connection_call (connection, - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - "Notify", - g_variant_new ("(susssasa{sv}i)", - "", /* app name */ - replace_id, - icon_name, - title, - body, - &action_builder, - &hints_builder, - -1), /* expire_timeout */ - G_VARIANT_TYPE ("(u)"), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, - callback, user_data); -} - -static void -notification_sent (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - FdoNotification *n = user_data; - GVariant *val; - GError *error = NULL; - static gboolean warning_printed = FALSE; - - val = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error); - if (val) - { - g_variant_get (val, "(u)", &n->notify_id); - g_variant_unref (val); - } - else - { - if (!warning_printed) - { - g_warning ("Unable to send notifications through org.freedesktop.Notifications: %s", - error->message); - warning_printed = TRUE; - } - - fdo_notifications = g_slist_remove (fdo_notifications, n); - fdo_notification_free (n); - - g_error_free (error); - } -} - static void handle_add_notification_fdo (XdpImplNotification *object, GDBusMethodInvocation *invocation, @@ -487,80 +167,33 @@ handle_add_notification_fdo (XdpImplNotification *object, const gchar *arg_id, GVariant *arg_notification) { - FdoNotification *n; GDBusConnection *connection; g_debug ("handle add-notification from %s using the freedesktop implementation", arg_app_id); connection = g_dbus_method_invocation_get_connection (invocation); - if (fdo_notify_subscription == 0) - { - fdo_notify_subscription = - g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.Notifications", - "org.freedesktop.Notifications", NULL, - "/org/freedesktop/Notifications", NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - notify_signal, NULL, NULL); - } - - n = fdo_find_notification (arg_app_id, arg_id); - if (n == NULL) - { - n = g_slice_new0 (FdoNotification); - n->app_id = g_strdup (arg_app_id); - n->id = g_strdup (arg_id); - n->notify_id = 0; - - fdo_notifications = g_slist_prepend (fdo_notifications, n); - } - else - { - /* Only clear default action. All other fields are still valid */ - g_clear_pointer (&n->default_action, g_free); - g_clear_pointer (&n->default_action_target, g_variant_unref); - } - - g_variant_lookup (arg_notification, "default-action", "s", &n->default_action); - n->default_action_target = g_variant_lookup_value (arg_notification, "default-action-target", G_VARIANT_TYPE_VARIANT); + fdo_add_notification (connection, arg_app_id, arg_id, arg_notification, activate_action, NULL); - call_notify (connection, - arg_app_id, - n->notify_id, - arg_notification, - notification_sent, n); + xdp_impl_notification_complete_add_notification (object, invocation); } -static void +static gboolean handle_remove_notification_fdo (XdpImplNotification *object, GDBusMethodInvocation *invocation, const gchar *arg_app_id, const gchar *arg_id) { - FdoNotification *n; - - g_debug ("handle remove-notification from %s using the freedesktop implementation", arg_app_id); + GDBusConnection *connection; - n = fdo_find_notification (arg_app_id, arg_id); - if (n) + connection = g_dbus_method_invocation_get_connection (invocation); + if (fdo_remove_notification (connection, arg_app_id, arg_id)) { - if (n->notify_id > 0) - { - g_dbus_connection_call (g_dbus_method_invocation_get_connection (invocation), - "org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - "CloseNotification", - g_variant_new ("(u)", n->id), - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); - } - - fdo_notifications = g_slist_remove (fdo_notifications, n); - fdo_notification_free (n); + g_debug ("handle remove-notification from %s using the freedesktop implementation", arg_app_id); + xdp_impl_notification_complete_remove_notification (object, invocation); + return TRUE; } + return FALSE; } static gboolean @@ -602,6 +235,7 @@ handle_add_notification (XdpImplNotification *object, handle_add_notification_fdo (object, invocation, arg_app_id, arg_id, arg_notification); else handle_add_notification_gtk (object, invocation, arg_app_id, arg_id, arg_notification); + return TRUE; } @@ -611,12 +245,7 @@ handle_remove_notification (XdpImplNotification *object, const gchar *arg_app_id, const gchar *arg_id) { - FdoNotification *n; - - n = fdo_find_notification (arg_app_id, arg_id); - if (n) - handle_remove_notification_fdo (object, invocation, arg_app_id, arg_id); - else + if (!handle_remove_notification_fdo (object, invocation, arg_app_id, arg_id)) handle_remove_notification_gtk (object, invocation, arg_app_id, arg_id); return TRUE; } diff --git a/src/xdg-desktop-portal-gtk.c b/src/xdg-desktop-portal-gtk.c index da839efa..9d066840 100644 --- a/src/xdg-desktop-portal-gtk.c +++ b/src/xdg-desktop-portal-gtk.c @@ -53,6 +53,7 @@ #include "screencast.h" #include "remotedesktop.h" #include "lockdown.h" +#include "background.h" static GMainLoop *loop = NULL; @@ -174,6 +175,12 @@ on_bus_acquired (GDBusConnection *connection, g_warning ("error: %s\n", error->message); g_clear_error (&error); } + + if (!background_init (connection, &error)) + { + g_warning ("error: %s\n", error->message); + g_clear_error (&error); + } } static void