/*
* 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
*/
#include "config.h"
#include
#include
#include
#include
#include "call.h"
#include "background.h"
#include "background-monitor.h"
#include "request.h"
#include "permissions.h"
#include "xdp-dbus.h"
#include "xdp-impl-dbus.h"
#include "xdp-utils.h"
#include "flatpak-instance.h"
/* Implementation notes:
*
* We store a YES/NO/ASK permission for "run in background".
*
* There is a portal api for apps to request this permission
* ahead of time. The portal also lets apps ask for being
* autostarted.
*
* We determine this condition by getting per-application
* state from the compositor, and comparing that list to
* the list of running flatpak instances obtained from
* $XDG_RUNTIME_DIR/.flatpak/. A thread is comparing
* this list every minute, and if it finds an app that
* is in the background twice, we take actions:
* - if the permission is NO, we kill it
* - if the permission is YES or ASK, we notify the user
*
* We only notify once per running instance to not be
* annoying.
*
* Platform-dependent parts are in the background portal
* backend:
* - Notifying the user
* - Getting compositor state
* - Enable or disable autostart
*/
#define PERMISSION_TABLE "background"
#define PERMISSION_ID "background"
typedef struct _Background Background;
typedef struct _BackgroundClass BackgroundClass;
struct _Background
{
XdpDbusBackgroundSkeleton parent_instance;
BackgroundMonitor *monitor;
};
struct _BackgroundClass
{
XdpDbusBackgroundSkeletonClass parent_class;
};
static XdpDbusImplAccess *access_impl;
static XdpDbusImplBackground *background_impl;
static Background *background;
static GFileMonitor *instance_monitor;
GType background_get_type (void) G_GNUC_CONST;
static void background_iface_init (XdpDbusBackgroundIface *iface);
G_DEFINE_TYPE_WITH_CODE (Background, background,
XDP_DBUS_TYPE_BACKGROUND_SKELETON,
G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_BACKGROUND,
background_iface_init));
typedef enum {
BACKGROUND = 0,
RUNNING = 1,
ACTIVE = 2,
} AppState;
typedef enum {
FORBID = 0,
ALLOW = 1,
IGNORE = 2
} NotifyResult;
typedef enum {
AUTOSTART_FLAGS_NONE = 0,
AUTOSTART_FLAGS_ACTIVATABLE = 1 << 0,
} AutostartFlags;
static GVariant *
get_all_permissions (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) out_perms = NULL;
g_autoptr(GVariant) out_data = NULL;
if (!xdp_dbus_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_one_permission (const char *app_id,
GVariant *perms)
{
const char **permissions;
if (perms == NULL)
{
g_debug ("No background permissions found");
return PERMISSION_UNSET;
}
else if (!g_variant_lookup (perms, app_id, "^a&s", &permissions))
{
g_debug ("No background permissions stored for: app %s", app_id);
return PERMISSION_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 PERMISSION_UNSET;
}
g_debug ("permission store: background, app %s -> %s", app_id, permissions[0]);
if (strcmp (permissions[0], "yes") == 0)
return PERMISSION_YES;
else if (strcmp (permissions[0], "no") == 0)
return PERMISSION_NO;
else if (strcmp (permissions[0], "ask") == 0)
return PERMISSION_ASK;
else
{
g_autofree char *a = g_strjoinv (" ", (char **)permissions);
g_warning ("Wrong permission format, ignoring (%s)", a);
}
return PERMISSION_UNSET;
}
static Permission
get_permission (const char *app_id)
{
g_autoptr(GVariant) perms = NULL;
perms = get_all_permissions ();
if (perms)
return get_one_permission (app_id, perms);
return PERMISSION_UNSET;
}
static void
set_permission (const char *app_id,
Permission permission)
{
g_autoptr(GError) error = NULL;
const char *permissions[2];
if (permission == PERMISSION_ASK)
permissions[0] = "ask";
else if (permission == PERMISSION_YES)
permissions[0] = "yes";
else if (permission == PERMISSION_NO)
permissions[0] = "no";
else
{
g_warning ("Wrong permission format, ignoring");
return;
}
permissions[1] = NULL;
if (!xdp_dbus_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);
}
}
/* background monitor */
/* The background monitor is running in a dedicated thread.
*
* We rely on the RunningApplicationsChanged signal from the backend to get
* notified about applications that start or stop having open windows, and on
* file monitoring to learn about flatpak instances appearing and disappearing.
*
* When either of these changes happens, we wake up the background monitor
* thread, and it will check the state of applications a few times, with a
* few seconds of wait in between. When we find an application in the background
* more than once, we check the permissions, and kill or notify if warranted.
*
* We require an application to be in background state for more than once check
* to avoid killing an unlucky application that just happened to start up as we
* did our check.
*/
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_dbus_impl_background_call_get_app_state_sync (background_impl, &apps, NULL, &error))
{
static int warned = 0;
if (!warned)
{
g_warning ("Failed to get application states: %s", error->message);
warned = 1;
}
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_one_app_state (const char *app_id,
GHashTable *app_states)
{
return (AppState)GPOINTER_TO_INT (g_hash_table_lookup (app_states, app_id));
}
typedef struct {
FlatpakInstance *instance;
int stamp;
AppState state;
char *handle;
gboolean notified;
Permission permission;
char *status_message;
} InstanceData;
static void
instance_data_free (gpointer data)
{
InstanceData *idata = data;
g_object_unref (idata->instance);
g_free (idata->status_message);
g_free (idata->handle);
g_free (idata);
}
/* instance ID -> InstanceData
*/
static GHashTable *applications;
G_LOCK_DEFINE (applications);
static void
close_notification (const char *handle)
{
g_dbus_connection_call (g_dbus_proxy_get_connection (G_DBUS_PROXY (background_impl)),
g_dbus_proxy_get_name (G_DBUS_PROXY (background_impl)),
handle,
"org.freedesktop.impl.portal.Request",
"Close",
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL, NULL, NULL);
}
static void
remove_outdated_instances (int stamp)
{
GHashTableIter iter;
char *id;
InstanceData *data;
g_autoptr(GPtrArray) handles = NULL;
int i;
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 *)&id, (gpointer *)&data))
{
if (data->stamp < stamp)
{
if (data->handle)
g_ptr_array_add (handles, g_strdup (data->handle));
g_hash_table_iter_remove (&iter);
}
}
G_UNLOCK (applications);
for (i = 0; i < handles->len; i++)
{
char *handle = g_ptr_array_index (handles, i);
close_notification (handle);
}
}
static void
update_background_monitor_properties (void)
{
GVariantBuilder builder;
GHashTableIter iter;
InstanceData *data;
char *id;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
G_LOCK (applications);
g_hash_table_iter_init (&iter, applications);
while (g_hash_table_iter_next (&iter, (gpointer *)&id, (gpointer *)&data))
{
GVariantBuilder app_builder;
const char *app_id;
const char *id;
if (data->state != BACKGROUND)
continue;
if (!flatpak_instance_is_running (data->instance))
continue;
id = flatpak_instance_get_id (data->instance);
app_id = flatpak_instance_get_app (data->instance);
g_assert (app_id != NULL);
g_variant_builder_init (&app_builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&app_builder, "{sv}", "app_id", g_variant_new_string (app_id));
g_variant_builder_add (&app_builder, "{sv}", "instance", g_variant_new_string (id));
if (data->status_message)
g_variant_builder_add (&app_builder, "{sv}", "message", g_variant_new_string (data->status_message));
g_variant_builder_add_value (&builder, g_variant_builder_end (&app_builder));
}
G_UNLOCK (applications);
xdp_dbus_background_monitor_set_background_apps (XDP_DBUS_BACKGROUND_MONITOR (background->monitor),
g_variant_builder_end (&builder));
}
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);
}
typedef struct {
char *handle;
char *app_id;
char *id;
char *name;
Permission perm;
pid_t child_pid;
} NotificationData;
static void
notification_data_free (gpointer data)
{
NotificationData *nd = data;
g_free (nd->handle);
g_free (nd->app_id);
g_free (nd->id);
g_free (nd->name);
g_free (nd);
}
static void
notify_background_done (GObject *source,
GAsyncResult *res,
gpointer data)
{
NotificationData *nd = (NotificationData *)data;
g_autoptr(GError) error = NULL;
g_autoptr(GVariant) results = NULL;
guint response;
guint result;
InstanceData *idata;
if (!xdp_dbus_impl_background_call_notify_background_finish (background_impl,
&response,
&results,
res,
&error))
{
g_dbus_error_strip_remote_error (error);
g_warning ("Error from background backend: %s", error->message);
notification_data_free (nd);
return;
}
if (!g_variant_lookup (results, "result", "u", &result))
result = IGNORE;
if (result == ALLOW)
{
g_debug ("Allowing app %s to run in background", nd->app_id);
if (nd->perm != PERMISSION_ASK)
nd->perm = PERMISSION_YES;
}
else if (result == FORBID)
{
g_debug ("Forbid app %s to run in background", nd->app_id);
if (nd->perm != PERMISSION_ASK)
nd->perm = PERMISSION_NO;
g_message ("Terminating app %s (process %d) because the app does not "
"have permission to run in the background. You may be able to "
"grant this app the permission to run in background in the "
"system settings of your desktop environment.",
nd->app_id, nd->child_pid);
kill (nd->child_pid, SIGKILL);
}
else if (result == IGNORE)
{
g_debug ("Allow this instance of %s to run in background without permission changes", nd->app_id);
}
else
g_debug ("Unexpected response from NotifyBackground: %u", result);
if (nd->perm != PERMISSION_UNSET)
set_permission (nd->app_id, nd->perm);
G_LOCK (applications);
idata = g_hash_table_lookup (applications, nd->id);
if (idata)
{
g_clear_pointer (&idata->handle, g_free);
idata->permission = nd->perm;
}
G_UNLOCK (applications);
notification_data_free (nd);
}
static void
check_background_apps (void)
{
g_autoptr(GVariant) perms = NULL;
g_autoptr(GHashTable) app_states = NULL;
g_autoptr(GPtrArray) instances = NULL;
int i;
static int stamp;
g_autoptr(GPtrArray) notifications = NULL;
app_states = get_app_states ();
if (app_states == NULL)
return;
g_debug ("Checking background permissions");
perms = get_all_permissions ();
instances = flatpak_instance_get_all ();
notifications = g_ptr_array_new ();
stamp++;
G_LOCK (applications);
for (i = 0; i < instances->len; i++)
{
FlatpakInstance *instance = g_ptr_array_index (instances, i);
const char *id;
const char *app_id;
pid_t child_pid;
InstanceData *idata;
const char *state_names[] = { "background", "running", "active" };
gboolean is_new = FALSE;
if (!flatpak_instance_is_running (instance))
continue;
id = flatpak_instance_get_id (instance);
app_id = flatpak_instance_get_app (instance);
child_pid = flatpak_instance_get_child_pid (instance);
idata = g_hash_table_lookup (applications, id);
if (!app_id)
continue;
if (!idata)
{
is_new = TRUE;
idata = g_new0 (InstanceData, 1);
idata->instance = g_object_ref (instance);
g_hash_table_insert (applications, g_strdup (id), idata);
}
idata->stamp = stamp;
idata->state = get_one_app_state (app_id, app_states);
g_debug ("App %s is %s", app_id, state_names[idata->state]);
idata->permission = get_one_permission (app_id, perms);
/* If the app is not in the list yet, add it,
* but don't notify yet - this gives apps some
* leeway to get their window up. If it is still
* in the background next time around, we'll proceed
* to the next step.
*/
if (idata->state != BACKGROUND || idata->notified || is_new)
{
if (idata->notified)
g_debug ("Already notified app %s ...skipping\n", app_id);
if (is_new)
g_debug ("App %s is new ...skipping\n", app_id);
continue;
}
switch (idata->permission)
{
case PERMISSION_NO:
idata->stamp = 0;
g_debug ("Kill app %s (pid %d)", app_id, child_pid);
kill (child_pid, SIGKILL);
break;
case PERMISSION_ASK:
case PERMISSION_UNSET:
{
NotificationData *nd = g_new0 (NotificationData, 1);
if (idata->handle)
{
close_notification (idata->handle);
g_free (idata->handle);
}
idata->handle = g_strdup_printf ("/org/freedesktop/portal/desktop/notify/background%d", stamp);
idata->notified = TRUE;
nd->handle = g_strdup (idata->handle);
nd->name = flatpak_instance_get_display_name (instance);
nd->app_id = g_strdup (app_id);
nd->id = g_strdup (id);
nd->child_pid = child_pid;
nd->perm = idata->permission;
g_ptr_array_add (notifications, nd);
}
break;
case PERMISSION_YES:
default:
break;
}
}
G_UNLOCK (applications);
for (i = 0; i < notifications->len; i++)
{
NotificationData *nd = g_ptr_array_index (notifications, i);
g_debug ("Notify background for %s", nd->app_id);
xdp_dbus_impl_background_call_notify_background (background_impl,
nd->handle,
nd->app_id,
nd->name,
NULL,
notify_background_done,
nd);
}
remove_outdated_instances (stamp);
update_background_monitor_properties ();
}
static GMainContext *monitor_context;
static gpointer
background_monitor (gpointer data)
{
applications = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, instance_data_free);
while (TRUE)
{
g_main_context_iteration (monitor_context, TRUE);
/* We check twice, to avoid killing unlucky apps hit at a bad time */
sleep (5);
check_background_apps ();
sleep (5);
check_background_apps ();
}
g_clear_pointer (&applications, g_hash_table_unref);
g_clear_pointer (&monitor_context, g_main_context_unref);
return NULL;
}
static void
start_background_monitor (void)
{
g_autoptr(GThread) thread = NULL;
g_debug ("Starting background app monitor");
monitor_context = g_main_context_new ();
thread = g_thread_new ("background monitor", background_monitor, NULL);
}
static void
running_apps_changed (gpointer data)
{
g_debug ("Running app windows changed, wake up monitor thread");
g_main_context_wakeup (monitor_context);
}
static void
instances_changed (gpointer data)
{
g_debug ("Running instances changed, wake up monitor thread");
g_main_context_wakeup (monitor_context);
}
static void
handle_request_background_in_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
Request *request = (Request *)task_data;
GVariant *options;
const char *app_id;
Permission permission;
const char *reason = NULL;
gboolean autostart_requested = FALSE;
gboolean autostart_enabled;
gboolean allowed;
g_autoptr(GError) error = NULL;
const char * const *autostart_exec = { NULL };
AutostartFlags autostart_flags = AUTOSTART_FLAGS_NONE;
gboolean activatable = FALSE;
g_auto(GStrv) commandline = NULL;
REQUEST_AUTOLOCK (request);
options = (GVariant *)g_object_get_data (G_OBJECT (request), "options");
g_variant_lookup (options, "reason", "&s", &reason);
g_variant_lookup (options, "autostart", "b", &autostart_requested);
g_variant_lookup (options, "commandline", "^a&s", &autostart_exec);
g_variant_lookup (options, "dbus-activatable", "b", &activatable);
if (activatable)
autostart_flags |= AUTOSTART_FLAGS_ACTIVATABLE;
app_id = xdp_app_info_get_id (request->app_info);
if (xdp_app_info_is_host (request->app_info))
permission = PERMISSION_YES;
else
permission = get_permission (app_id);
g_debug ("Handle RequestBackground for '%s'", app_id);
if (permission == PERMISSION_ASK)
{
GVariantBuilder opt_builder;
g_autofree char *title = NULL;
g_autofree char *subtitle = NULL;
g_autofree char *body = NULL;
guint32 response = 2;
g_autoptr(GVariant) results = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GAppInfo) info = NULL;
info = xdp_app_info_load_app_info (request->app_info);
title = g_strdup_printf (_("Allow %s to run in the background?"), info ? g_app_info_get_display_name (info) : app_id);
if (reason)
subtitle = g_strdup (reason);
else if (autostart_requested)
subtitle = g_strdup_printf (_("%s requests to be started automatically and run in the background."), info ? g_app_info_get_display_name (info) : app_id);
else
subtitle = g_strdup_printf (_("%s requests to run in the background."), info ? g_app_info_get_display_name (info) : app_id);
body = g_strdup (_("The ‘run in background’ permission can be changed at any time from the application settings."));
g_debug ("Calling backend for background access for: %s", app_id);
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Don't allow")));
g_variant_builder_add (&opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow")));
if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl,
request->id,
app_id,
"",
title,
subtitle,
body,
g_variant_builder_end (&opt_builder),
&response,
&results,
NULL,
&error))
{
g_warning ("AccessDialog call failed: %s", error->message);
g_clear_error (&error);
}
allowed = response == 0;
}
else
{
allowed = permission != PERMISSION_NO;
if (permission == PERMISSION_UNSET)
set_permission (app_id, PERMISSION_YES);
}
g_debug ("Setting autostart for %s to %s", app_id,
allowed && autostart_requested ? "enabled" : "disabled");
autostart_enabled = FALSE;
commandline = xdp_app_info_rewrite_commandline (request->app_info, autostart_exec,
FALSE /* don't quote escape */);
if (commandline == NULL)
{
g_debug ("Autostart not supported for: %s", app_id);
}
else if (!xdp_dbus_impl_background_call_enable_autostart_sync (background_impl,
app_id,
allowed && autostart_requested,
(const char * const *)commandline,
autostart_flags,
&autostart_enabled,
NULL,
&error))
{
g_warning ("EnableAutostart call failed: %s", error->message);
g_clear_error (&error);
}
if (request->exported)
{
XdgDesktopPortalResponseEnum portal_response;
GVariantBuilder results;
g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&results, "{sv}", "background", g_variant_new_boolean (allowed));
g_variant_builder_add (&results, "{sv}", "autostart", g_variant_new_boolean (autostart_enabled));
if (allowed)
portal_response = XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS;
else
portal_response = XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED;
xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request),
portal_response,
g_variant_builder_end (&results));
request_unexport (request);
}
}
static gboolean
validate_reason (const char *key,
GVariant *value,
GVariant *options,
GError **error)
{
const char *string = g_variant_get_string (value, NULL);
if (g_utf8_strlen (string, -1) > 256)
{
g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Not accepting overly long reasons");
return FALSE;
}
return TRUE;
}
static gboolean
validate_commandline (const char *key,
GVariant *value,
GVariant *options,
GError **error)
{
gsize length;
const char **strv = g_variant_get_strv (value, &length);
if (strv[0] == NULL)
{
g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Commandline can't be empty");
return FALSE;
}
if (g_utf8_strlen (strv[0], -1) > 256)
{
g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Not accepting overly long commandlines");
return FALSE;
}
if (length > 100)
{
g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Not accepting overly long commandlines");
return FALSE;
}
return TRUE;
}
static XdpOptionKey background_options[] = {
{ "reason", G_VARIANT_TYPE_STRING, validate_reason },
{ "autostart", G_VARIANT_TYPE_BOOLEAN, NULL },
{ "commandline", G_VARIANT_TYPE_STRING_ARRAY, validate_commandline },
{ "dbus-activatable", G_VARIANT_TYPE_BOOLEAN, NULL },
};
static gboolean
handle_request_background (XdpDbusBackground *object,
GDBusMethodInvocation *invocation,
const char *arg_window,
GVariant *arg_options)
{
Request *request = request_from_invocation (invocation);
g_autoptr(GError) error = NULL;
g_autoptr(XdpDbusImplRequest) impl_request = NULL;
g_autoptr(GTask) task = NULL;
GVariantBuilder opt_builder;
g_autoptr(GVariant) options = NULL;
REQUEST_AUTOLOCK (request);
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
if (!xdp_filter_options (arg_options, &opt_builder,
background_options, G_N_ELEMENTS (background_options),
&error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
options = g_variant_ref_sink (g_variant_builder_end (&opt_builder));
g_object_set_data_full (G_OBJECT (request), "window", g_strdup (arg_window), g_free);
g_object_set_data_full (G_OBJECT (request), "options", g_variant_ref (options), (GDestroyNotify)g_variant_unref);
impl_request =
xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (access_impl)),
G_DBUS_PROXY_FLAGS_NONE,
g_dbus_proxy_get_name (G_DBUS_PROXY (access_impl)),
request->id,
NULL, &error);
if (!impl_request)
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
request_set_impl_request (request, impl_request);
request_export (request, g_dbus_method_invocation_get_connection (invocation));
xdp_dbus_background_complete_request_background (object, invocation, request->id);
task = g_task_new (object, NULL, NULL, NULL);
g_task_set_task_data (task, g_object_ref (request), g_object_unref);
g_task_run_in_thread (task, handle_request_background_in_thread_func);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static void
set_status_finished_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GDBusMethodInvocation *invocation;
g_autoptr(GError) error = NULL;
g_assert (g_task_is_valid (result, source_object));
invocation = g_task_get_task_data (G_TASK (result));
g_assert (invocation != NULL);
if (g_task_propagate_boolean (G_TASK (result), &error))
{
xdp_dbus_background_complete_set_status (XDP_DBUS_BACKGROUND (source_object),
invocation);
}
else
{
g_dbus_method_invocation_return_gerror (invocation, error);
}
}
static void
handle_set_status_in_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GDBusMethodInvocation *invocation = task_data;
g_autofree char *message = NULL;
InstanceData *data;
const char *id = NULL;
GVariant *options;
Call *call;
call = call_from_invocation (invocation);
id = xdp_app_info_get_instance (call->app_info);
options = g_object_get_data (G_OBJECT (invocation), "options");
g_variant_lookup (options, "message", "s", &message);
G_LOCK (applications);
data = g_hash_table_lookup (applications, id);
if (!data)
{
g_autoptr(GHashTable) app_states = NULL;
g_autoptr(GPtrArray) instances = NULL;
FlatpakInstance *instance = NULL;
instances = flatpak_instance_get_all ();
for (guint i = 0; i < instances->len; i++)
{
FlatpakInstance *aux = g_ptr_array_index (instances, i);
if (g_strcmp0 (id, flatpak_instance_get_id (aux)) == 0)
{
instance = aux;
break;
}
}
if (!instance)
{
G_UNLOCK (applications);
g_task_return_new_error (task,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_FAILED,
"No sandboxed instance of the application found");
return;
}
app_states = get_app_states ();
if (app_states == NULL)
{
G_UNLOCK (applications);
g_task_return_new_error (task,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_FAILED,
"Could not fetch app state from backend");
return;
}
data = g_new0 (InstanceData, 1);
data->instance = g_object_ref (instance);
data->state = get_one_app_state (xdp_app_info_get_id (call->app_info), app_states);
g_hash_table_insert (applications, g_strdup (id), data);
}
g_assert (data != NULL);
g_clear_pointer (&data->status_message, g_free);
data->status_message = g_steal_pointer (&message);
G_UNLOCK (applications);
update_background_monitor_properties ();
g_task_return_boolean (task, TRUE);
}
static gboolean
validate_message (const char *key,
GVariant *value,
GVariant *options,
GError **error)
{
const char *string = g_variant_get_string (value, NULL);
if (g_utf8_strlen (string, -1) > 96)
{
g_set_error (error,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Status message is longer than 96 characters");
return FALSE;
}
if (strstr (string, "\n"))
{
g_set_error (error,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
"Status message must not have newlines");
return FALSE;
}
return TRUE;
}
static XdpOptionKey set_status_options[] = {
{ "message", G_VARIANT_TYPE_STRING, validate_message },
};
static gboolean
handle_set_status (XdpDbusBackground *object,
GDBusMethodInvocation *invocation,
GVariant *arg_options)
{
g_autoptr(GVariant) options = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GTask) task = NULL;
GVariantBuilder opt_builder;
const char *id = NULL;
Call *call;
call = call_from_invocation (invocation);
g_debug ("Handling SetStatus call from %s", xdp_app_info_get_id (call->app_info));
if (xdp_app_info_is_host (call->app_info))
{
g_dbus_method_invocation_return_error (invocation,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_NOT_ALLOWED,
"Only sandboxed applications can set background status");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
id = xdp_app_info_get_instance (call->app_info);
if (!id)
{
g_dbus_method_invocation_return_error (invocation,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_FAILED,
"No sandboxed instance of the application found");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
if (!xdp_filter_options (arg_options, &opt_builder,
set_status_options,
G_N_ELEMENTS (set_status_options),
&error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
options = g_variant_ref_sink (g_variant_builder_end (&opt_builder));
g_object_set_data_full (G_OBJECT (invocation),
"options",
g_steal_pointer (&options),
(GDestroyNotify) g_variant_unref);
task = g_task_new (object, NULL, set_status_finished_cb, NULL);
g_task_set_task_data (task, g_object_ref (invocation), g_object_unref);
g_task_run_in_thread (task, handle_set_status_in_thread_func);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static void
background_iface_init (XdpDbusBackgroundIface *iface)
{
iface->handle_request_background = handle_request_background;
iface->handle_set_status = handle_set_status;
}
static void
background_init (Background *background)
{
xdp_dbus_background_set_version (XDP_DBUS_BACKGROUND (background), 2);
}
static void
background_class_init (BackgroundClass *klass)
{
}
GDBusInterfaceSkeleton *
background_create (GDBusConnection *connection,
const char *dbus_name_access,
const char *dbus_name_background)
{
g_autofree char *instance_path = NULL;
g_autoptr(GFile) instance_dir = NULL;
g_autoptr(GError) error = NULL;
access_impl = xdp_dbus_impl_access_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
dbus_name_access,
DESKTOP_PORTAL_OBJECT_PATH,
NULL,
&error);
if (access_impl == NULL)
{
g_warning ("Failed to create access proxy: %s", error->message);
return NULL;
}
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (access_impl), G_MAXINT);
background_impl = xdp_dbus_impl_background_proxy_new_sync (connection,
G_DBUS_PROXY_FLAGS_NONE,
dbus_name_background,
DESKTOP_PORTAL_OBJECT_PATH,
NULL,
&error);
if (background_impl == NULL)
{
g_warning ("Failed to create background proxy: %s", error->message);
return NULL;
}
g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (background_impl), G_MAXINT);
background = g_object_new (background_get_type (), NULL);
background->monitor = background_monitor_new (NULL, &error);
if (background->monitor == NULL)
{
g_warning ("Failed to create background monitor: %s", error->message);
return NULL;
}
start_background_monitor ();
g_signal_connect (background_impl, "running-applications-changed",
G_CALLBACK (running_apps_changed), NULL);
/* FIXME: it would be better if libflatpak had a monitor api for this */
instance_path = g_build_filename (g_get_user_runtime_dir (), ".flatpak", NULL);
instance_dir = g_file_new_for_path (instance_path);
instance_monitor = g_file_monitor_directory (instance_dir, G_FILE_MONITOR_NONE, NULL, &error);
if (!instance_monitor)
g_warning ("Failed to create a monitor for %s: %s", instance_path, error->message);
else
g_signal_connect (instance_monitor, "changed", G_CALLBACK (instances_changed), NULL);
return G_DBUS_INTERFACE_SKELETON (background);
}