diff --git a/data/Makefile.am.inc b/data/Makefile.am.inc
index bfb7fc6a6..1c0c4fa23 100644
--- a/data/Makefile.am.inc
+++ b/data/Makefile.am.inc
@@ -36,4 +36,5 @@ dist_introspection_DATA = \
data/org.freedesktop.impl.portal.RemoteDesktop.xml \
data/org.freedesktop.impl.portal.Settings.xml \
data/org.freedesktop.impl.portal.Lockdown.xml \
+ data/org.freedesktop.impl.portal.Background.xml \
$(NULL)
diff --git a/data/org.freedesktop.impl.portal.Background.xml b/data/org.freedesktop.impl.portal.Background.xml
new file mode 100644
index 000000000..3ee8bf771
--- /dev/null
+++ b/data/org.freedesktop.impl.portal.Background.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Makefile.am.inc b/src/Makefile.am.inc
index e05fcf825..beb963d7e 100644
--- a/src/Makefile.am.inc
+++ b/src/Makefile.am.inc
@@ -55,6 +55,7 @@ PORTAL_IMPL_IFACE_FILES =\
data/org.freedesktop.impl.portal.RemoteDesktop.xml \
data/org.freedesktop.impl.portal.Settings.xml \
data/org.freedesktop.impl.portal.Lockdown.xml \
+ data/org.freedesktop.impl.portal.Background.xml \
$(NULL)
$(xdp_dbus_built_sources) : $(PORTAL_IFACE_FILES)
@@ -150,6 +151,8 @@ xdg_desktop_portal_SOURCES = \
src/fc-monitor.h \
src/background.c \
src/background.h \
+ src/background-monitor.c \
+ src/background-monitor.h \
$(NULL)
if HAVE_PIPEWIRE
@@ -174,6 +177,7 @@ xdg_desktop_portal_LDADD = \
$(BASE_LIBS) \
$(PIPEWIRE_LIBS) \
$(GEOCLUE_LIBS) \
+ $(FLATPAK_LIBS) \
$(NULL)
xdg_desktop_portal_CFLAGS = \
-DPKGDATADIR=\"$(pkgdatadir)\" \
@@ -182,6 +186,7 @@ xdg_desktop_portal_CFLAGS = \
$(BASE_CFLAGS) \
$(PIPEWIRE_CFLAGS) \
$(GEOCLUE_CFLAGS) \
+ $(FLATPAK_CFLAGS) \
-I$(srcdir)/src \
-I$(builddir)/src \
$(NULL)
diff --git a/src/background-monitor.c b/src/background-monitor.c
new file mode 100644
index 000000000..4e54bb33a
--- /dev/null
+++ b/src/background-monitor.c
@@ -0,0 +1,505 @@
+/*
+ * 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.1 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
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#include
+#include "background-monitor.h"
+#include "permissions.h"
+#include "xdp-impl-dbus.h"
+#include "xdp-utils.h"
+
+#define PERMISSION_TABLE "background"
+#define PERMISSION_ID "background"
+
+typedef enum { UNSET, NO, YES, ASK } Permission;
+
+static XdpImplBackground *background = NULL;
+
+typedef enum { BACKGROUND, RUNNING, ACTIVE } AppState;
+
+static GHashTable *
+get_app_states (void)
+{
+ g_autoptr(GVariant) apps = NULL;
+ g_autoptr(GHashTable) app_states = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ const char *appid;
+ GVariant *value;
+ g_autoptr(GError) error = NULL;
+
+ if (!xdp_impl_background_call_get_app_state_sync (background, &apps, NULL, &error))
+ {
+ g_warning ("Failed to get application states: %s", error->message);
+ return NULL;
+ }
+
+ g_autoptr(GVariantIter) iter = g_variant_iter_new (apps);
+ while (g_variant_iter_loop (iter, "{&sv}", &appid, &value))
+ {
+ AppState state = g_variant_get_uint32 (value);
+ g_hash_table_insert (app_states, g_strdup (appid), GINT_TO_POINTER (state));
+ }
+
+ return g_steal_pointer (&app_states);
+}
+
+static AppState
+get_app_state (const char *app_id,
+ GHashTable *app_states)
+{
+ return (AppState)GPOINTER_TO_INT (g_hash_table_lookup (app_states, app_id));
+}
+
+static GVariant *
+get_permissions (void)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) out_perms = NULL;
+ g_autoptr(GVariant) out_data = NULL;
+
+ if (!xdp_impl_permission_store_call_lookup_sync (get_permission_store (),
+ PERMISSION_TABLE,
+ PERMISSION_ID,
+ &out_perms,
+ &out_data,
+ NULL,
+ &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_debug ("No background permissions found: %s", error->message);
+ return NULL;
+ }
+
+ return g_steal_pointer (&out_perms);
+}
+
+static Permission
+get_permission (const char *app_id,
+ GVariant *perms)
+{
+ const char **permissions;
+
+ if (perms == NULL)
+ {
+ g_debug ("No background permissions found");
+
+ return UNSET;
+ }
+ else if (!g_variant_lookup (perms, app_id, "^a&s", &permissions))
+ {
+ g_debug ("No background permissions stored for: app %s", app_id);
+
+ return UNSET;
+ }
+ else if (g_strv_length ((char **)permissions) != 1)
+ {
+ g_autofree char *a = g_strjoinv (" ", (char **)permissions);
+ g_warning ("Wrong background permission format, ignoring (%s)", a);
+ return UNSET;
+ }
+ g_debug ("permission store: background, app %s -> %s", app_id, permissions[0]);
+
+ if (strcmp (permissions[0], "yes") == 0)
+ return YES;
+ else if (strcmp (permissions[0], "no") == 0)
+ return NO;
+ else if (strcmp (permissions[0], "ask") == 0)
+ return ASK;
+ else
+ {
+ g_autofree char *a = g_strjoinv (" ", (char **)permissions);
+ g_warning ("Wrong permission format, ignoring (%s)", a);
+ }
+
+ return UNSET;
+}
+
+static void
+set_permission (const char *app_id,
+ Permission permission)
+{
+ g_autoptr(GError) error = NULL;
+ const char *permissions[2];
+
+ if (permission == ASK)
+ permissions[0] = "ask";
+ else if (permission == YES)
+ permissions[0] = "yes";
+ else if (permission == NO)
+ permissions[0] = "no";
+ else
+ {
+ g_warning ("Wrong permission format, ignoring");
+ return;
+ }
+ permissions[1] = NULL;
+
+ if (!xdp_impl_permission_store_call_set_permission_sync (get_permission_store (),
+ PERMISSION_TABLE,
+ TRUE,
+ PERMISSION_ID,
+ app_id,
+ (const char * const*)permissions,
+ NULL,
+ &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("Error updating permission store: %s", error->message);
+ }
+}
+
+static GHashTable *applications;
+G_LOCK_DEFINE_STATIC (applications);
+
+static void
+close_notification (const char *handle)
+{
+ g_dbus_connection_call (g_dbus_proxy_get_connection (G_DBUS_PROXY (background)),
+ g_dbus_proxy_get_name (G_DBUS_PROXY (background)),
+ handle,
+ "org.freedesktop.impl.portal.Request",
+ "Close",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL, NULL, NULL);
+}
+
+static void
+remove_outdated_applications (GPtrArray *apps)
+{
+ int j;
+ GHashTableIter iter;
+ char *app_id;
+ char *handle;
+ g_autoptr(GPtrArray) handles = NULL;
+
+ handles = g_ptr_array_new_with_free_func (g_free);
+
+ G_LOCK (applications);
+ g_hash_table_iter_init (&iter, applications);
+ while (g_hash_table_iter_next (&iter, (gpointer *)&app_id, (gpointer *)&handle))
+ {
+ gboolean found = FALSE;
+ for (j = 0; j < apps->len && !found; j++)
+ {
+ FlatpakInstance *app = g_ptr_array_index (apps, j);
+ found = g_strcmp0 (app_id, flatpak_instance_get_app (app));
+ }
+ if (!found)
+ {
+ g_hash_table_iter_remove (&iter);
+ if (handle)
+ g_ptr_array_add (handles, g_strdup (handle));
+ }
+ }
+ G_UNLOCK (applications);
+
+ for (j = 0; j < handles->len; j++)
+ {
+ const char *handle = g_ptr_array_index (handles, j);
+ close_notification (handle);
+ }
+}
+
+static gboolean
+lookup_background_app (const char *app_id,
+ char **value)
+{
+ gboolean res;
+ char *orig_key;
+ char *orig_val;
+
+ G_LOCK (applications);
+ res = g_hash_table_lookup_extended (applications, app_id, (gpointer *)&orig_key, (gpointer *)&orig_val);
+ if (res)
+ *value = g_strdup (orig_val);
+ G_UNLOCK (applications);
+
+ return res;
+}
+
+static void
+add_background_app (const char *app_id,
+ const char *handle)
+{
+ G_LOCK (applications);
+ g_hash_table_insert (applications, g_strdup (app_id), g_strdup (handle));
+ G_UNLOCK (applications);
+}
+
+static void
+remove_background_app (const char *app_id)
+{
+ g_autofree char *handle = NULL;
+
+ G_LOCK (applications);
+ handle = g_strdup (g_hash_table_lookup (applications, app_id));
+ g_hash_table_remove (applications, app_id);
+ G_UNLOCK (applications);
+
+ if (handle)
+ close_notification (handle);
+}
+
+static char *
+flatpak_instance_get_display_name (FlatpakInstance *instance)
+{
+ const char *app_id = flatpak_instance_get_app (instance);
+ if (app_id[0] != 0)
+ {
+ g_autofree char *desktop_id = NULL;
+ g_autoptr(GAppInfo) info = NULL;
+
+ desktop_id = g_strconcat (app_id, ".desktop", NULL);
+ info = (GAppInfo*)g_desktop_app_info_new (desktop_id);
+
+ if (info)
+ return g_strdup (g_app_info_get_display_name (info));
+ }
+
+ return g_strdup (app_id);
+}
+
+static FlatpakInstance *
+find_instance (const char *app_id)
+{
+ g_autoptr(GPtrArray) instances = NULL;
+ int i;
+
+ instances = flatpak_instance_get_all ();
+ for (i = 0; i < instances->len; i++)
+ {
+ FlatpakInstance *inst = g_ptr_array_index (instances, i);
+
+ if (g_str_equal (flatpak_instance_get_app (inst), app_id))
+ return g_object_ref (inst);
+ }
+
+ return NULL;
+}
+
+static void
+kill_app (const char *app_id)
+{
+ g_autoptr(FlatpakInstance) instance = NULL;
+
+ g_debug ("Killing app %s", app_id);
+
+ instance = find_instance (app_id);
+
+ if (instance)
+ kill (flatpak_instance_get_child_pid (instance), SIGKILL);
+}
+
+typedef struct {
+ char *app_id;
+ Permission perm;
+} DoneData;
+
+static void
+done_data_free (gpointer data)
+{
+ DoneData *ddata = data;
+
+ g_free (ddata->app_id);
+ g_free (ddata);
+}
+
+static void
+notify_background_done (GObject *source,
+ GAsyncResult *res,
+ gpointer data)
+{
+ DoneData *ddata = (DoneData *)data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) results = NULL;
+ guint response;
+ gboolean allow;
+
+ if (!xdp_impl_background_call_notify_background_finish (background,
+ &response,
+ &results,
+ res,
+ &error))
+ {
+ g_warning ("Error from background backend: %s", error->message);
+ done_data_free (ddata);
+ return;
+ }
+
+ g_variant_lookup (results, "allow", "b", &allow);
+
+ if (allow)
+ {
+ g_debug ("Allowing app %s to run in background", ddata->app_id);
+ if (ddata->perm != ASK)
+ set_permission (ddata->app_id, YES);
+ }
+ else
+ {
+ g_debug ("Forbid app %s to run in background", ddata->app_id);
+
+ if (ddata->perm != ASK)
+ set_permission (ddata->app_id, ASK);
+ kill_app (ddata->app_id);
+ }
+
+ add_background_app (ddata->app_id, NULL);
+ done_data_free (ddata);
+}
+
+static void
+send_notification (FlatpakInstance *instance,
+ Permission permission)
+{
+ DoneData *ddata;
+ g_autofree char *name = flatpak_instance_get_display_name (instance);
+ g_autofree char *handle = NULL;
+ static int count;
+
+ ddata = g_new (DoneData, 1);
+ ddata->app_id = g_strdup (flatpak_instance_get_app (instance));
+ ddata->perm = permission;
+
+ g_debug ("Notify background for %s", ddata->app_id);
+
+ handle = g_strdup_printf ("/org/freedesktop/portal/desktop/notify/background%d", count++);
+
+ add_background_app (ddata->app_id, handle);
+
+ xdp_impl_background_call_notify_background (background,
+ handle,
+ ddata->app_id,
+ name,
+ NULL,
+ notify_background_done,
+ ddata);
+}
+
+static void
+check_background_apps (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GVariant) perms = NULL;
+ g_autoptr(GHashTable) app_states = NULL;
+ g_autoptr(GPtrArray) apps = NULL;
+ int i;
+
+ app_states = get_app_states ();
+
+ if (app_states == NULL)
+ return;
+
+ g_debug ("Checking background permissions");
+ perms = get_permissions ();
+ apps = flatpak_instance_get_all ();
+
+ remove_outdated_applications (apps);
+
+ for (i = 0; i < apps->len; i++)
+ {
+ FlatpakInstance *instance = g_ptr_array_index (apps, i);
+ const char *app_id = flatpak_instance_get_app (instance);
+ Permission permission;
+ AppState state;
+ const char *state_names[] = { "background", "running", "active" };
+ g_autofree char *handle = NULL;
+
+ if (!flatpak_instance_is_running (instance))
+ continue;
+
+ state = get_app_state (app_id, app_states);
+ g_debug ("App %s is %s", app_id, state_names[state]);
+
+ if (state != BACKGROUND)
+ {
+ remove_background_app (app_id);
+ continue;
+ }
+
+ if (!lookup_background_app (app_id, &handle))
+ {
+ add_background_app (app_id, NULL);
+ continue;
+ }
+
+ if (handle)
+ continue; /* already notified */
+
+ permission = get_permission (app_id, perms);
+ if (permission == NO)
+ {
+ pid_t pid = flatpak_instance_get_child_pid (instance);
+ g_debug ("Killing app %s (child pid %u)", app_id, pid);
+ kill (pid, SIGKILL);
+ }
+ else if (permission == ASK || permission == UNSET)
+ {
+ send_notification (instance, permission);
+ }
+ }
+}
+
+static gboolean
+enforce_background_permissions (gpointer data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_run_in_thread (task, check_background_apps);
+
+ return G_SOURCE_CONTINUE;
+}
+
+void
+start_background_monitor (GDBusConnection *connection,
+ const char *dbus_name)
+{
+ g_autoptr(GError) error = NULL;
+
+ background = xdp_impl_background_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ dbus_name,
+ DESKTOP_PORTAL_OBJECT_PATH,
+ NULL, &error);
+ if (background == NULL)
+ {
+ g_debug ("No background impl: %s", error->message);
+ return;
+ }
+
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (background), G_MAXINT);
+
+ init_permission_store (connection);
+
+ applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ g_debug ("Starting background app monitor");
+ g_timeout_add_seconds (30, enforce_background_permissions, NULL);
+ enforce_background_permissions (NULL);
+}
diff --git a/src/background-monitor.h b/src/background-monitor.h
new file mode 100644
index 000000000..7b733a002
--- /dev/null
+++ b/src/background-monitor.h
@@ -0,0 +1,26 @@
+/*
+ * 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.1 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
+ */
+#ifndef __BACKGROUND_MONITOR_H__
+#define __BACKGROUND_MONITOR_H__
+
+void start_background_monitor (GDBusConnection *connection,
+ const char *dbus_name);
+
+#endif
diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c
index 9a1745062..1795a671b 100644
--- a/src/xdg-desktop-portal.c
+++ b/src/xdg-desktop-portal.c
@@ -51,6 +51,7 @@
#include "location.h"
#include "settings.h"
#include "background.h"
+#include "background-monitor.h"
static GMainLoop *loop = NULL;
@@ -469,6 +470,10 @@ on_bus_acquired (GDBusConnection *connection,
export_portal_implementation (connection,
remote_desktop_create (connection, implementation->dbus_name));
#endif
+
+ implementation = find_portal_implementation ("org.freedesktop.impl.portal.Background");
+ if (implementation != NULL)
+ start_background_monitor (connection, implementation->dbus_name);
}
static void