notification: Use Desktop Portal notification when running in sandbox
When an application is running from a snap or a flatpak, libnotify should just work as a FDO Portal Notification wrapper. As per this, make the library to try use the portal API in sandboxed applications, by emulating the needed missing pieces. Not everything can be achieved 1:1, but it's just safer to use in such contexts.
This commit is contained in:
parent
c6534a144c
commit
f3eb807464
|
@ -26,6 +26,10 @@
|
||||||
#define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
|
#define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
|
||||||
#define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
|
#define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
|
||||||
|
|
||||||
|
#define NOTIFY_PORTAL_DBUS_NAME "org.freedesktop.portal.Desktop"
|
||||||
|
#define NOTIFY_PORTAL_DBUS_CORE_INTERFACE "org.freedesktop.portal.Notification"
|
||||||
|
#define NOTIFY_PORTAL_DBUS_CORE_OBJECT "/org/freedesktop/portal/desktop"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
GDBusProxy * _notify_get_proxy (GError **error);
|
GDBusProxy * _notify_get_proxy (GError **error);
|
||||||
|
@ -40,6 +44,10 @@ const char * _notify_get_snap_name (void);
|
||||||
const char * _notify_get_snap_path (void);
|
const char * _notify_get_snap_path (void);
|
||||||
const char * _notify_get_snap_app (void);
|
const char * _notify_get_snap_app (void);
|
||||||
|
|
||||||
|
const char * _notify_get_flatpak_app (void);
|
||||||
|
|
||||||
|
gboolean _notify_uses_portal_notifications (void);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* _LIBNOTIFY_INTERNAL_H_ */
|
#endif /* _LIBNOTIFY_INTERNAL_H_ */
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
static void notify_notification_class_init (NotifyNotificationClass *klass);
|
static void notify_notification_class_init (NotifyNotificationClass *klass);
|
||||||
static void notify_notification_init (NotifyNotification *sp);
|
static void notify_notification_init (NotifyNotification *sp);
|
||||||
static void notify_notification_finalize (GObject *object);
|
static void notify_notification_finalize (GObject *object);
|
||||||
|
static void notify_notification_dispose (GObject *object);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -72,6 +73,7 @@ struct _NotifyNotificationPrivate
|
||||||
|
|
||||||
/* NULL to use icon data. Anything else to have server lookup icon */
|
/* NULL to use icon data. Anything else to have server lookup icon */
|
||||||
char *icon_name;
|
char *icon_name;
|
||||||
|
GdkPixbuf *icon_pixbuf;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -1 = use server default
|
* -1 = use server default
|
||||||
|
@ -79,6 +81,7 @@ struct _NotifyNotificationPrivate
|
||||||
* > 0 = Number of milliseconds before we timeout
|
* > 0 = Number of milliseconds before we timeout
|
||||||
*/
|
*/
|
||||||
gint timeout;
|
gint timeout;
|
||||||
|
guint portal_timeout_id;
|
||||||
|
|
||||||
GSList *actions;
|
GSList *actions;
|
||||||
GHashTable *action_map;
|
GHashTable *action_map;
|
||||||
|
@ -150,6 +153,7 @@ notify_notification_class_init (NotifyNotificationClass *klass)
|
||||||
object_class->constructor = notify_notification_constructor;
|
object_class->constructor = notify_notification_constructor;
|
||||||
object_class->get_property = notify_notification_get_property;
|
object_class->get_property = notify_notification_get_property;
|
||||||
object_class->set_property = notify_notification_set_property;
|
object_class->set_property = notify_notification_set_property;
|
||||||
|
object_class->dispose = notify_notification_dispose;
|
||||||
object_class->finalize = notify_notification_finalize;
|
object_class->finalize = notify_notification_finalize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -369,6 +373,22 @@ notify_notification_init (NotifyNotification *obj)
|
||||||
(GDestroyNotify) destroy_pair);
|
(GDestroyNotify) destroy_pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
notify_notification_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
NotifyNotification *obj = NOTIFY_NOTIFICATION (object);
|
||||||
|
NotifyNotificationPrivate *priv = obj->priv;
|
||||||
|
|
||||||
|
if (priv->portal_timeout_id) {
|
||||||
|
g_source_remove (priv->portal_timeout_id);
|
||||||
|
priv->portal_timeout_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_object (&priv->icon_pixbuf);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
notify_notification_finalize (GObject *object)
|
notify_notification_finalize (GObject *object)
|
||||||
{
|
{
|
||||||
|
@ -405,6 +425,18 @@ notify_notification_finalize (GObject *object)
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
maybe_warn_portal_unsupported_feature (const char *feature_name)
|
||||||
|
{
|
||||||
|
if (!_notify_uses_portal_notifications ()) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_message ("%s is not available when using Portal Notifications",
|
||||||
|
feature_name);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* notify_notification_new:
|
* notify_notification_new:
|
||||||
* @summary: The required summary text.
|
* @summary: The required summary text.
|
||||||
|
@ -592,6 +624,33 @@ notify_notification_update (NotifyNotification *notification,
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
get_portal_notification_id (NotifyNotification *notification)
|
||||||
|
{
|
||||||
|
char *app_id;
|
||||||
|
char *notification_id;
|
||||||
|
|
||||||
|
g_assert (_notify_uses_portal_notifications ());
|
||||||
|
|
||||||
|
if (_notify_get_snap_name ()) {
|
||||||
|
app_id = g_strdup_printf ("snap.%s_%s",
|
||||||
|
_notify_get_snap_name (),
|
||||||
|
_notify_get_snap_app ());
|
||||||
|
} else {
|
||||||
|
app_id = g_strdup_printf ("flatpak.%s",
|
||||||
|
_notify_get_flatpak_app ());
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_id = g_strdup_printf ("libnotify-%s-%s-%u",
|
||||||
|
app_id,
|
||||||
|
notify_get_app_name (),
|
||||||
|
notification->priv->id);
|
||||||
|
|
||||||
|
g_free (app_id);
|
||||||
|
|
||||||
|
return notification_id;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
activate_action (NotifyNotification *notification,
|
activate_action (NotifyNotification *notification,
|
||||||
const gchar *action)
|
const gchar *action)
|
||||||
|
@ -646,8 +705,12 @@ proxy_g_signal_cb (GDBusProxy *proxy,
|
||||||
GVariant *parameters,
|
GVariant *parameters,
|
||||||
NotifyNotification *notification)
|
NotifyNotification *notification)
|
||||||
{
|
{
|
||||||
|
const char *interface;
|
||||||
|
|
||||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||||
|
|
||||||
|
interface = g_dbus_proxy_get_interface_name (proxy);
|
||||||
|
|
||||||
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
|
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
|
||||||
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
|
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
|
||||||
guint32 id, reason;
|
guint32 id, reason;
|
||||||
|
@ -658,6 +721,7 @@ proxy_g_signal_cb (GDBusProxy *proxy,
|
||||||
|
|
||||||
close_notification (notification, reason);
|
close_notification (notification, reason);
|
||||||
} else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
|
} else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
|
||||||
|
g_str_equal (interface, NOTIFY_DBUS_CORE_INTERFACE) &&
|
||||||
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
|
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
|
||||||
guint32 id;
|
guint32 id;
|
||||||
const char *action;
|
const char *action;
|
||||||
|
@ -683,7 +747,304 @@ proxy_g_signal_cb (GDBusProxy *proxy,
|
||||||
|
|
||||||
g_free (notification->priv->activation_token);
|
g_free (notification->priv->activation_token);
|
||||||
notification->priv->activation_token = g_strdup (activation_token);
|
notification->priv->activation_token = g_strdup (activation_token);
|
||||||
|
} else if (g_str_equal (signal_name, "ActionInvoked") &&
|
||||||
|
g_str_equal (interface, NOTIFY_PORTAL_DBUS_CORE_INTERFACE) &&
|
||||||
|
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssav)"))) {
|
||||||
|
char *notification_id;
|
||||||
|
const char *id;
|
||||||
|
const char *action;
|
||||||
|
GVariant *parameter;
|
||||||
|
|
||||||
|
g_variant_get (parameters, "(&s&s@av)", &id, &action, ¶meter);
|
||||||
|
g_variant_unref (parameter);
|
||||||
|
|
||||||
|
notification_id = get_portal_notification_id (notification);
|
||||||
|
|
||||||
|
if (!g_str_equal (notification_id, id)) {
|
||||||
|
g_free (notification_id);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!activate_action (notification, action) &&
|
||||||
|
g_str_equal (action, "default-action") &&
|
||||||
|
!_notify_get_snap_app ()) {
|
||||||
|
g_warning ("Received unknown action %s", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
close_notification (notification, NOTIFY_CLOSED_REASON_DISMISSED);
|
||||||
|
|
||||||
|
g_free (notification_id);
|
||||||
|
} else {
|
||||||
|
g_debug ("Unhandled signal '%s.%s'", interface, signal_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
remove_portal_notification (GDBusProxy *proxy,
|
||||||
|
NotifyNotification *notification,
|
||||||
|
NotifyClosedReason reason,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
GVariant *ret;
|
||||||
|
gchar *notification_id;
|
||||||
|
|
||||||
|
if (notification->priv->portal_timeout_id) {
|
||||||
|
g_source_remove (notification->priv->portal_timeout_id);
|
||||||
|
notification->priv->portal_timeout_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_id = get_portal_notification_id (notification);
|
||||||
|
|
||||||
|
ret = g_dbus_proxy_call_sync (proxy,
|
||||||
|
"RemoveNotification",
|
||||||
|
g_variant_new ("(s)", notification_id),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
-1,
|
||||||
|
NULL,
|
||||||
|
error);
|
||||||
|
|
||||||
|
g_free (notification_id);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
close_notification (notification, reason);
|
||||||
|
|
||||||
|
g_variant_unref (ret);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_portal_timeout (gpointer data)
|
||||||
|
{
|
||||||
|
NotifyNotification *notification = data;
|
||||||
|
GDBusProxy *proxy;
|
||||||
|
|
||||||
|
notification->priv->portal_timeout_id = 0;
|
||||||
|
|
||||||
|
proxy = _notify_get_proxy (NULL);
|
||||||
|
if (proxy == NULL) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_portal_notification (proxy, notification,
|
||||||
|
NOTIFY_CLOSED_REASON_EXPIRED, NULL);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GIcon *
|
||||||
|
get_notification_gicon (NotifyNotification *notification,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
NotifyNotificationPrivate *priv = notification->priv;
|
||||||
|
GFileInputStream *input;
|
||||||
|
GFile *file = NULL;
|
||||||
|
GIcon *gicon = NULL;
|
||||||
|
|
||||||
|
if (priv->icon_pixbuf) {
|
||||||
|
return G_ICON (g_object_ref (priv->icon_pixbuf));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!priv->icon_name) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr (priv->icon_name, "://")) {
|
||||||
|
file = g_file_new_for_uri (priv->icon_name);
|
||||||
|
} else if (g_file_test (priv->icon_name, G_FILE_TEST_EXISTS)) {
|
||||||
|
file = g_file_new_for_path (priv->icon_name);
|
||||||
|
} else {
|
||||||
|
gicon = g_themed_icon_new (priv->icon_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return gicon;
|
||||||
|
}
|
||||||
|
|
||||||
|
input = g_file_read (file, NULL, error);
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
GByteArray *bytes_array = g_byte_array_new ();
|
||||||
|
guint8 buf[1024];
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
gssize read;
|
||||||
|
|
||||||
|
read = g_input_stream_read (G_INPUT_STREAM (input),
|
||||||
|
buf,
|
||||||
|
G_N_ELEMENTS (buf),
|
||||||
|
NULL, NULL);
|
||||||
|
|
||||||
|
if (read > 0) {
|
||||||
|
g_byte_array_append (bytes_array, buf, read);
|
||||||
|
} else {
|
||||||
|
if (read < 0) {
|
||||||
|
g_byte_array_unref (bytes_array);
|
||||||
|
bytes_array = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_array && bytes_array->len) {
|
||||||
|
GBytes *bytes;
|
||||||
|
|
||||||
|
bytes = g_byte_array_free_to_bytes (bytes_array);
|
||||||
|
bytes_array = NULL;
|
||||||
|
|
||||||
|
gicon = g_bytes_icon_new (bytes);
|
||||||
|
} else if (bytes_array) {
|
||||||
|
g_byte_array_unref (bytes_array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_object (&input);
|
||||||
|
g_clear_object (&file);
|
||||||
|
|
||||||
|
return gicon;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
add_portal_notification (GDBusProxy *proxy,
|
||||||
|
NotifyNotification *notification,
|
||||||
|
GError **error)
|
||||||
|
{
|
||||||
|
GIcon *icon;
|
||||||
|
GVariant *urgency;
|
||||||
|
GVariant *ret;
|
||||||
|
GVariantBuilder builder;
|
||||||
|
NotifyNotificationPrivate *priv = notification->priv;
|
||||||
|
GError *local_error = NULL;
|
||||||
|
static guint32 portal_notification_count = 0;
|
||||||
|
char *notification_id;
|
||||||
|
|
||||||
|
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
|
||||||
|
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "title",
|
||||||
|
g_variant_new_string (priv->summary ? priv->summary : ""));
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "body",
|
||||||
|
g_variant_new_string (priv->body ? priv->body : ""));
|
||||||
|
|
||||||
|
if (g_hash_table_lookup (priv->action_map, "default")) {
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "default-action",
|
||||||
|
g_variant_new_string ("default"));
|
||||||
|
} else if (g_hash_table_lookup (priv->action_map, "DEFAULT")) {
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "default-action",
|
||||||
|
g_variant_new_string ("DEFAULT"));
|
||||||
|
} else if (_notify_get_snap_app ()) {
|
||||||
|
/* In the snap case we may need to ensure that a default-action
|
||||||
|
* is set to ensure that we will use the FDO notification daemon
|
||||||
|
* and won't fallback to GTK one, as app-id won't match.
|
||||||
|
* See: https://github.com/flatpak/xdg-desktop-portal/issues/769
|
||||||
|
*/
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "default-action",
|
||||||
|
g_variant_new_string ("snap-fake-default-action"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv->has_nondefault_actions) {
|
||||||
|
GVariantBuilder buttons;
|
||||||
|
GSList *l;
|
||||||
|
|
||||||
|
g_variant_builder_init (&buttons, G_VARIANT_TYPE ("aa{sv}"));
|
||||||
|
|
||||||
|
for (l = priv->actions; l && l->next; l = l->next->next) {
|
||||||
|
GVariantBuilder button;
|
||||||
|
const char *action;
|
||||||
|
const char *label;
|
||||||
|
|
||||||
|
g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT);
|
||||||
|
|
||||||
|
action = l->data;
|
||||||
|
label = l->next->data;
|
||||||
|
|
||||||
|
g_variant_builder_add (&button, "{sv}", "action",
|
||||||
|
g_variant_new_string (action));
|
||||||
|
g_variant_builder_add (&button, "{sv}", "label",
|
||||||
|
g_variant_new_string (label));
|
||||||
|
|
||||||
|
g_variant_builder_add (&buttons, "@a{sv}",
|
||||||
|
g_variant_builder_end (&button));
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "buttons",
|
||||||
|
g_variant_builder_end (&buttons));
|
||||||
|
}
|
||||||
|
|
||||||
|
urgency = g_hash_table_lookup (notification->priv->hints, "urgency");
|
||||||
|
if (urgency) {
|
||||||
|
switch (g_variant_get_byte (urgency)) {
|
||||||
|
case NOTIFY_URGENCY_LOW:
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "priority",
|
||||||
|
g_variant_new_string ("low"));
|
||||||
|
break;
|
||||||
|
case NOTIFY_URGENCY_NORMAL:
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "priority",
|
||||||
|
g_variant_new_string ("normal"));
|
||||||
|
break;
|
||||||
|
case NOTIFY_URGENCY_CRITICAL:
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "priority",
|
||||||
|
g_variant_new_string ("urgent"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_warn_if_reached ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
icon = get_notification_gicon (notification, &local_error);
|
||||||
|
if (icon) {
|
||||||
|
GVariant *serialized_icon = g_icon_serialize (icon);
|
||||||
|
|
||||||
|
g_variant_builder_add (&builder, "{sv}", "icon",
|
||||||
|
serialized_icon);
|
||||||
|
g_variant_unref (serialized_icon);
|
||||||
|
g_clear_object (&icon);
|
||||||
|
} else if (local_error) {
|
||||||
|
g_propagate_error (error, local_error);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!priv->id) {
|
||||||
|
priv->id = ++portal_notification_count;
|
||||||
|
} else if (priv->closed_reason == NOTIFY_CLOSED_REASON_UNSET) {
|
||||||
|
remove_portal_notification (proxy, notification,
|
||||||
|
NOTIFY_CLOSED_REASON_UNSET, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_id = get_portal_notification_id (notification);
|
||||||
|
|
||||||
|
ret = g_dbus_proxy_call_sync (proxy,
|
||||||
|
"AddNotification",
|
||||||
|
g_variant_new ("(s@a{sv})",
|
||||||
|
notification_id,
|
||||||
|
g_variant_builder_end (&builder)),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
|
-1,
|
||||||
|
NULL,
|
||||||
|
error);
|
||||||
|
|
||||||
|
if (priv->portal_timeout_id) {
|
||||||
|
g_source_remove (priv->portal_timeout_id);
|
||||||
|
priv->portal_timeout_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (notification_id);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv->timeout > 0) {
|
||||||
|
priv->portal_timeout_id = g_timeout_add (priv->timeout,
|
||||||
|
on_portal_timeout,
|
||||||
|
notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref (ret);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -731,6 +1092,10 @@ notify_notification_show (NotifyNotification *notification,
|
||||||
notification);
|
notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_notify_uses_portal_notifications ()) {
|
||||||
|
return add_portal_notification (proxy, notification, error);
|
||||||
|
}
|
||||||
|
|
||||||
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
|
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
|
||||||
for (l = priv->actions; l != NULL; l = l->next) {
|
for (l = priv->actions; l != NULL; l = l->next) {
|
||||||
g_variant_builder_add (&actions_builder, "s", l->data);
|
g_variant_builder_add (&actions_builder, "s", l->data);
|
||||||
|
@ -854,6 +1219,10 @@ notify_notification_set_category (NotifyNotification *notification,
|
||||||
g_return_if_fail (notification != NULL);
|
g_return_if_fail (notification != NULL);
|
||||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||||
|
|
||||||
|
if (maybe_warn_portal_unsupported_feature ("Category")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (category != NULL && category[0] != '\0') {
|
if (category != NULL && category[0] != '\0') {
|
||||||
notify_notification_set_hint_string (notification,
|
notify_notification_set_hint_string (notification,
|
||||||
"category",
|
"category",
|
||||||
|
@ -931,11 +1300,18 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
|
||||||
hint_name = "icon_data";
|
hint_name = "icon_data";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_clear_object (¬ification->priv->icon_pixbuf);
|
||||||
|
|
||||||
if (pixbuf == NULL) {
|
if (pixbuf == NULL) {
|
||||||
notify_notification_set_hint (notification, hint_name, NULL);
|
notify_notification_set_hint (notification, hint_name, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_notify_uses_portal_notifications ()) {
|
||||||
|
notification->priv->icon_pixbuf = g_object_ref (pixbuf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
g_object_get (pixbuf,
|
g_object_get (pixbuf,
|
||||||
"width", &width,
|
"width", &width,
|
||||||
"height", &height,
|
"height", &height,
|
||||||
|
@ -1059,6 +1435,10 @@ notify_notification_set_app_name (NotifyNotification *notification,
|
||||||
{
|
{
|
||||||
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
|
||||||
|
|
||||||
|
if (maybe_warn_portal_unsupported_feature ("App Name")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
g_free (notification->priv->app_name);
|
g_free (notification->priv->app_name);
|
||||||
notification->priv->app_name = g_strdup (app_name);
|
notification->priv->app_name = g_strdup (app_name);
|
||||||
|
|
||||||
|
@ -1357,6 +1737,12 @@ notify_notification_close (NotifyNotification *notification,
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_notify_uses_portal_notifications ()) {
|
||||||
|
return remove_portal_notification (proxy, notification,
|
||||||
|
NOTIFY_CLOSED_REASON_API_REQUEST,
|
||||||
|
error);
|
||||||
|
}
|
||||||
|
|
||||||
/* FIXME: make this nonblocking! */
|
/* FIXME: make this nonblocking! */
|
||||||
result = g_dbus_proxy_call_sync (proxy,
|
result = g_dbus_proxy_call_sync (proxy,
|
||||||
"CloseNotification",
|
"CloseNotification",
|
||||||
|
|
|
@ -45,10 +45,12 @@ static gboolean _initted = FALSE;
|
||||||
static char *_app_name = NULL;
|
static char *_app_name = NULL;
|
||||||
static char *_snap_name = NULL;
|
static char *_snap_name = NULL;
|
||||||
static char *_snap_app = NULL;
|
static char *_snap_app = NULL;
|
||||||
|
static char *_flatpak_app = NULL;
|
||||||
static GDBusProxy *_proxy = NULL;
|
static GDBusProxy *_proxy = NULL;
|
||||||
static GList *_active_notifications = NULL;
|
static GList *_active_notifications = NULL;
|
||||||
static int _spec_version_major = 0;
|
static int _spec_version_major = 0;
|
||||||
static int _spec_version_minor = 0;
|
static int _spec_version_minor = 0;
|
||||||
|
static int _portal_version = 0;
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
_notify_check_spec_version (int major,
|
_notify_check_spec_version (int major,
|
||||||
|
@ -76,6 +78,26 @@ _notify_get_server_info (char **ret_name,
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_notify_uses_portal_notifications ()) {
|
||||||
|
if (ret_name) {
|
||||||
|
*ret_name = g_strdup ("Portal Notification");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret_vendor) {
|
||||||
|
*ret_vendor = g_strdup ("Freedesktop");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret_version) {
|
||||||
|
*ret_version = g_strdup_printf ("%u", _portal_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret_spec_version) {
|
||||||
|
*ret_spec_version = g_strdup ("1.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
result = g_dbus_proxy_call_sync (proxy,
|
result = g_dbus_proxy_call_sync (proxy,
|
||||||
"GetServerInformation",
|
"GetServerInformation",
|
||||||
g_variant_new ("()"),
|
g_variant_new ("()"),
|
||||||
|
@ -327,6 +349,81 @@ _notify_get_snap_app (void)
|
||||||
return _snap_app;
|
return _snap_app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
_notify_get_flatpak_app (void)
|
||||||
|
{
|
||||||
|
static gsize flatpak_app_set = FALSE;
|
||||||
|
|
||||||
|
if (g_once_init_enter (&flatpak_app_set)) {
|
||||||
|
GKeyFile *info = g_key_file_new ();
|
||||||
|
|
||||||
|
if (g_key_file_load_from_file (info, "/.flatpak-info",
|
||||||
|
G_KEY_FILE_NONE, NULL)) {
|
||||||
|
const char *group = "Application";
|
||||||
|
|
||||||
|
if (g_key_file_has_group (info, "Runtime")) {
|
||||||
|
group = "Runtime";
|
||||||
|
}
|
||||||
|
|
||||||
|
_flatpak_app = g_key_file_get_string (info, group,
|
||||||
|
"name", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_key_file_free (info);
|
||||||
|
g_once_init_leave (&flatpak_app_set, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _flatpak_app;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
_notify_is_running_under_flatpak (void)
|
||||||
|
{
|
||||||
|
return !!_notify_get_flatpak_app ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
_notify_is_running_under_snap (void)
|
||||||
|
{
|
||||||
|
return !!_notify_get_snap_app ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
_notify_is_running_in_sandbox (void)
|
||||||
|
{
|
||||||
|
static gsize use_portal = 0;
|
||||||
|
enum {
|
||||||
|
IGNORE_PORTAL = 1,
|
||||||
|
TRY_USE_PORTAL = 2,
|
||||||
|
FORCE_PORTAL = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
if (g_once_init_enter (&use_portal)) {
|
||||||
|
if (G_UNLIKELY (g_getenv ("NOTIFY_IGNORE_PORTAL"))) {
|
||||||
|
g_once_init_leave (&use_portal, IGNORE_PORTAL);
|
||||||
|
} else if (G_UNLIKELY (g_getenv ("NOTIFY_FORCE_PORTAL"))) {
|
||||||
|
g_once_init_leave (&use_portal, FORCE_PORTAL);
|
||||||
|
} else {
|
||||||
|
g_once_init_leave (&use_portal, TRY_USE_PORTAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_portal == IGNORE_PORTAL) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return use_portal == FORCE_PORTAL ||
|
||||||
|
_notify_is_running_under_flatpak () ||
|
||||||
|
_notify_is_running_under_snap ();
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
_notify_uses_portal_notifications (void)
|
||||||
|
{
|
||||||
|
return _portal_version != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* notify_get_app_name:
|
* notify_get_app_name:
|
||||||
*
|
*
|
||||||
|
@ -382,6 +479,9 @@ notify_uninit (void)
|
||||||
g_free (_snap_app);
|
g_free (_snap_app);
|
||||||
_snap_app = NULL;
|
_snap_app = NULL;
|
||||||
|
|
||||||
|
g_free (_flatpak_app);
|
||||||
|
_flatpak_app = NULL;
|
||||||
|
|
||||||
_initted = FALSE;
|
_initted = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,6 +498,46 @@ notify_is_initted (void)
|
||||||
return _initted;
|
return _initted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GDBusProxy *
|
||||||
|
_get_portal_proxy (GError **error)
|
||||||
|
{
|
||||||
|
GError *local_error = NULL;
|
||||||
|
GDBusProxy *proxy;
|
||||||
|
GVariant *res;
|
||||||
|
|
||||||
|
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
|
||||||
|
G_DBUS_PROXY_FLAGS_NONE,
|
||||||
|
NULL,
|
||||||
|
NOTIFY_PORTAL_DBUS_NAME,
|
||||||
|
NOTIFY_PORTAL_DBUS_CORE_OBJECT,
|
||||||
|
NOTIFY_PORTAL_DBUS_CORE_INTERFACE,
|
||||||
|
NULL,
|
||||||
|
&local_error);
|
||||||
|
|
||||||
|
if (proxy == NULL) {
|
||||||
|
g_debug ("Failed to get portal proxy: %s", local_error->message);
|
||||||
|
g_clear_error (&local_error);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = g_dbus_proxy_get_cached_property (proxy, "version");
|
||||||
|
if (!res) {
|
||||||
|
g_object_unref (proxy);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_portal_version = g_variant_get_uint32 (res);
|
||||||
|
g_assert (_portal_version > 0);
|
||||||
|
|
||||||
|
g_warning ("Running in confined mode, using Portal notifications. "
|
||||||
|
"Some features and hints won't be supported");
|
||||||
|
|
||||||
|
g_variant_unref (res);
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* _notify_get_proxy:
|
* _notify_get_proxy:
|
||||||
* @error: (allow-none): a location to store a #GError, or %NULL
|
* @error: (allow-none): a location to store a #GError, or %NULL
|
||||||
|
@ -413,6 +553,14 @@ _notify_get_proxy (GError **error)
|
||||||
if (_proxy != NULL)
|
if (_proxy != NULL)
|
||||||
return _proxy;
|
return _proxy;
|
||||||
|
|
||||||
|
if (_notify_is_running_in_sandbox ()) {
|
||||||
|
_proxy = _get_portal_proxy (error);
|
||||||
|
|
||||||
|
if (_proxy != NULL) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
|
_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
|
||||||
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
|
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
|
||||||
NULL,
|
NULL,
|
||||||
|
@ -421,6 +569,8 @@ _notify_get_proxy (GError **error)
|
||||||
NOTIFY_DBUS_CORE_INTERFACE,
|
NOTIFY_DBUS_CORE_INTERFACE,
|
||||||
NULL,
|
NULL,
|
||||||
error);
|
error);
|
||||||
|
|
||||||
|
out:
|
||||||
if (_proxy == NULL) {
|
if (_proxy == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -458,6 +608,15 @@ notify_get_server_caps (void)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_notify_uses_portal_notifications ()) {
|
||||||
|
list = g_list_prepend (list, g_strdup ("actions"));
|
||||||
|
list = g_list_prepend (list, g_strdup ("body"));
|
||||||
|
list = g_list_prepend (list, g_strdup ("body-images"));
|
||||||
|
list = g_list_prepend (list, g_strdup ("icon-static"));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
result = g_dbus_proxy_call_sync (proxy,
|
result = g_dbus_proxy_call_sync (proxy,
|
||||||
"GetCapabilities",
|
"GetCapabilities",
|
||||||
g_variant_new ("()"),
|
g_variant_new ("()"),
|
||||||
|
|
Loading…
Reference in New Issue