2016-07-01 01:12:30 -04:00
|
|
|
|
/*
|
|
|
|
|
* Copyright © 2016 Red Hat, Inc.
|
|
|
|
|
*
|
2022-06-01 12:44:23 +01:00
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
|
*
|
2016-07-01 01:12:30 -04:00
|
|
|
|
* This library 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
|
2017-05-27 18:21:30 +02:00
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2016-07-01 01:12:30 -04:00
|
|
|
|
*
|
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
*
|
|
|
|
|
* Author: Matthias Clasen
|
2024-11-18 16:25:17 +01:00
|
|
|
|
* Julian Sparber <jsparber@gnome.org>
|
2016-07-01 01:12:30 -04:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#include "gnotificationbackend.h"
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
|
|
#include <glib.h>
|
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
|
|
2016-07-01 01:12:30 -04:00
|
|
|
|
#include "giomodule-priv.h"
|
2024-05-02 12:50:46 +02:00
|
|
|
|
#include "gioenumtypes.h"
|
|
|
|
|
#include "gicon.h"
|
2016-07-01 01:12:30 -04:00
|
|
|
|
#include "gdbusconnection.h"
|
|
|
|
|
#include "gapplication.h"
|
|
|
|
|
#include "gnotification-private.h"
|
|
|
|
|
#include "gportalsupport.h"
|
2024-11-05 18:51:29 +01:00
|
|
|
|
#include "gtask.h"
|
2024-11-18 16:25:17 +01:00
|
|
|
|
#include "gunixfdlist.h"
|
|
|
|
|
#include <gio/gunixoutputstream.h>
|
|
|
|
|
|
|
|
|
|
/* This is the max size the xdg portal allows for icons, so load icons with this size when needed */
|
|
|
|
|
#define ICON_SIZE 512
|
2016-07-01 01:12:30 -04:00
|
|
|
|
|
|
|
|
|
#define G_TYPE_PORTAL_NOTIFICATION_BACKEND (g_portal_notification_backend_get_type ())
|
|
|
|
|
#define G_PORTAL_NOTIFICATION_BACKEND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_PORTAL_NOTIFICATION_BACKEND, GPortalNotificationBackend))
|
|
|
|
|
|
|
|
|
|
typedef struct _GPortalNotificationBackend GPortalNotificationBackend;
|
|
|
|
|
typedef GNotificationBackendClass GPortalNotificationBackendClass;
|
|
|
|
|
|
|
|
|
|
struct _GPortalNotificationBackend
|
|
|
|
|
{
|
|
|
|
|
GNotificationBackend parent;
|
2024-11-05 18:51:29 +01:00
|
|
|
|
|
|
|
|
|
guint version;
|
2016-07-01 01:12:30 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
GType g_portal_notification_backend_get_type (void);
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GPortalNotificationBackend, g_portal_notification_backend, G_TYPE_NOTIFICATION_BACKEND,
|
|
|
|
|
_g_io_modules_ensure_extension_points_registered ();
|
|
|
|
|
g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME,
|
|
|
|
|
g_define_type_id, "portal", 110))
|
|
|
|
|
|
2024-11-05 18:51:29 +01:00
|
|
|
|
static void
|
|
|
|
|
get_properties_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
|
|
|
|
GPortalNotificationBackend *backend = g_task_get_source_object (task);
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GVariant *ret;
|
|
|
|
|
GVariant *vardict;
|
|
|
|
|
|
|
|
|
|
ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error);
|
|
|
|
|
if (!ret)
|
|
|
|
|
{
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_clear_object (&task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_get (ret, "(@a{sv})", &vardict);
|
|
|
|
|
|
|
|
|
|
if (!g_variant_lookup (vardict, "version", "u", &backend->version))
|
|
|
|
|
backend->version = 1;
|
|
|
|
|
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&ret, g_variant_unref);
|
|
|
|
|
g_clear_pointer (&vardict, g_variant_unref);
|
|
|
|
|
g_clear_object (&task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
get_supported_features (GPortalNotificationBackend *backend,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task;
|
|
|
|
|
|
|
|
|
|
task = g_task_new (backend, NULL, callback, user_data);
|
|
|
|
|
g_task_set_source_tag (task, get_supported_features);
|
|
|
|
|
|
|
|
|
|
if (backend->version != 0)
|
|
|
|
|
{
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
g_clear_object (&task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_dbus_connection_call (G_NOTIFICATION_BACKEND (backend)->dbus_connection,
|
|
|
|
|
"org.freedesktop.portal.Desktop",
|
|
|
|
|
"/org/freedesktop/portal/desktop",
|
|
|
|
|
"org.freedesktop.DBus.Properties",
|
|
|
|
|
"GetAll",
|
|
|
|
|
g_variant_new ("(s)", "org.freedesktop.portal.Notification"),
|
|
|
|
|
G_VARIANT_TYPE ("(a{sv})"),
|
|
|
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
|
|
|
-1,
|
|
|
|
|
g_task_get_cancellable (task),
|
|
|
|
|
get_properties_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
|
|
|
|
|
g_clear_object (&task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
get_supported_features_finish (GPortalNotificationBackend *backend,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
g_return_val_if_fail (G_IS_NOTIFICATION_BACKEND (backend), FALSE);
|
|
|
|
|
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
|
|
|
|
|
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == get_supported_features, FALSE);
|
|
|
|
|
|
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
typedef struct {
|
|
|
|
|
char *id;
|
|
|
|
|
GNotification *notification;
|
|
|
|
|
} CallData;
|
|
|
|
|
|
|
|
|
|
static CallData*
|
|
|
|
|
call_data_new (const char *id,
|
|
|
|
|
GNotification *notification)
|
|
|
|
|
{
|
|
|
|
|
CallData *data;
|
|
|
|
|
|
|
|
|
|
data = g_new0 (CallData, 1);
|
|
|
|
|
data->id = g_strdup (id);
|
|
|
|
|
data->notification = g_object_ref (notification);
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-05 18:51:29 +01:00
|
|
|
|
static void
|
2024-11-18 16:25:17 +01:00
|
|
|
|
call_data_free (gpointer user_data)
|
2024-11-05 18:51:29 +01:00
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
CallData *data = user_data;
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&data->id, g_free);
|
|
|
|
|
g_clear_object (&data->notification);
|
|
|
|
|
|
|
|
|
|
g_free (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
GUnixFDList *fd_list;
|
|
|
|
|
GVariantBuilder *builder;
|
|
|
|
|
gsize parse_ref;
|
|
|
|
|
} ParserData;
|
|
|
|
|
|
|
|
|
|
static ParserData*
|
|
|
|
|
parser_data_new (const char *id)
|
|
|
|
|
{
|
|
|
|
|
ParserData *data;
|
|
|
|
|
|
|
|
|
|
data = g_new0 (ParserData, 1);
|
|
|
|
|
data->fd_list = g_unix_fd_list_new ();
|
|
|
|
|
data->builder = g_variant_builder_new (G_VARIANT_TYPE ("(sa{sv})"));
|
|
|
|
|
|
|
|
|
|
g_variant_builder_add (data->builder, "s", id);
|
|
|
|
|
g_variant_builder_open (data->builder, G_VARIANT_TYPE ("a{sv}"));
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
parser_data_hold (ParserData *data)
|
|
|
|
|
{
|
|
|
|
|
data->parse_ref++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
parser_data_release (ParserData *data)
|
|
|
|
|
{
|
|
|
|
|
data->parse_ref--;
|
|
|
|
|
|
|
|
|
|
if (data->parse_ref == 0)
|
|
|
|
|
g_variant_builder_close (data->builder);
|
|
|
|
|
|
|
|
|
|
return data->parse_ref == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
parser_data_free (gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
ParserData *data = user_data;
|
|
|
|
|
|
|
|
|
|
g_clear_object (&data->fd_list);
|
|
|
|
|
g_clear_pointer (&data->builder, g_variant_builder_unref);
|
|
|
|
|
|
|
|
|
|
g_free (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
splice_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *res,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
2024-11-05 18:51:29 +01:00
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
if (g_output_stream_splice_finish (G_OUTPUT_STREAM (source_object), res, &error) == -1)
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
else
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
icon_load_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *res,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
|
|
|
|
GOutputStream *stream_out = G_OUTPUT_STREAM (g_task_get_task_data (task));
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GInputStream *stream_in = NULL;
|
|
|
|
|
|
|
|
|
|
stream_in = g_loadable_icon_load_finish (G_LOADABLE_ICON (source_object), res, NULL, &error);
|
|
|
|
|
if (!stream_in)
|
2024-11-05 18:51:29 +01:00
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
2024-11-05 18:51:29 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_output_stream_splice_async (stream_out,
|
|
|
|
|
G_INPUT_STREAM (stream_in),
|
|
|
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
|
|
|
|
|
g_task_get_priority (task),
|
|
|
|
|
g_task_get_cancellable (task),
|
|
|
|
|
splice_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
|
|
|
|
|
g_object_unref (stream_in);
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
bytes_to_memfd (const gchar *name,
|
|
|
|
|
GBytes *bytes,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
int fd = -1;
|
|
|
|
|
gconstpointer bytes_data;
|
|
|
|
|
gpointer shm;
|
|
|
|
|
gsize bytes_len;
|
|
|
|
|
|
|
|
|
|
fd = memfd_create (name, MFD_ALLOW_SEALING);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_set_error (error,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"memfd_create: %s", g_strerror (saved_errno));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes_data = g_bytes_get_data (bytes, &bytes_len);
|
|
|
|
|
|
|
|
|
|
if (ftruncate (fd, bytes_len) == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_set_error (error,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"ftruncate: %s", g_strerror (saved_errno));
|
|
|
|
|
close (fd);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shm = mmap (NULL, bytes_len, PROT_WRITE, MAP_SHARED, fd, 0);
|
|
|
|
|
if (shm == MAP_FAILED)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_set_error (error,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"mmap: %s", g_strerror (saved_errno));
|
|
|
|
|
close (fd);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy (shm, bytes_data, bytes_len);
|
|
|
|
|
|
|
|
|
|
if (munmap (shm, bytes_len) == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_set_error (error,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"munmap: %s", g_strerror (saved_errno));
|
|
|
|
|
close (fd);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g_steal_fd (&fd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
serialize_icon (GIcon *icon,
|
|
|
|
|
uint version,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = NULL;
|
|
|
|
|
|
|
|
|
|
task = g_task_new (NULL, NULL, callback, user_data);
|
|
|
|
|
g_task_set_source_tag (task, serialize_icon);
|
|
|
|
|
|
|
|
|
|
if (G_IS_THEMED_ICON (icon))
|
|
|
|
|
{
|
|
|
|
|
g_task_set_task_data (task,
|
|
|
|
|
g_icon_serialize (icon),
|
|
|
|
|
(GDestroyNotify) g_variant_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
else if (G_IS_BYTES_ICON (icon))
|
|
|
|
|
{
|
|
|
|
|
if (version < 2)
|
|
|
|
|
{
|
|
|
|
|
g_task_set_task_data (task,
|
|
|
|
|
g_icon_serialize (icon),
|
|
|
|
|
(GDestroyNotify) g_variant_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
GOutputStream *stream_out = NULL;
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
fd = bytes_to_memfd ("notification-icon",
|
|
|
|
|
g_bytes_icon_get_bytes (G_BYTES_ICON (icon)),
|
|
|
|
|
&error);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
{
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream_out = g_unix_output_stream_new (g_steal_fd (&fd), TRUE);
|
|
|
|
|
g_task_set_task_data (task, g_steal_pointer (&stream_out), g_object_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (G_IS_LOADABLE_ICON (icon))
|
|
|
|
|
{
|
|
|
|
|
GOutputStream *stream_out = NULL;
|
|
|
|
|
|
|
|
|
|
if (version < 2)
|
|
|
|
|
{
|
|
|
|
|
stream_out = g_memory_output_stream_new_resizable ();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
fd = memfd_create ("notification-icon", MFD_ALLOW_SEALING);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_task_return_new_error (task,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"memfd_create: %s", g_strerror (saved_errno));
|
|
|
|
|
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream_out = g_unix_output_stream_new (g_steal_fd (&fd), TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_task_set_task_data (task, g_steal_pointer (&stream_out), g_object_unref);
|
|
|
|
|
|
|
|
|
|
g_loadable_icon_load_async (G_LOADABLE_ICON (icon),
|
|
|
|
|
ICON_SIZE,
|
|
|
|
|
NULL,
|
|
|
|
|
icon_load_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GVariant*
|
|
|
|
|
serialize_icon_finish (GAsyncResult *result,
|
|
|
|
|
GUnixFDList *fd_list,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (result);
|
|
|
|
|
gpointer data = g_task_get_task_data (task);
|
|
|
|
|
GVariant *res = NULL;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (g_task_get_source_tag (task) == serialize_icon, NULL);
|
|
|
|
|
|
|
|
|
|
if (!g_task_propagate_boolean (task, error))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
g_assert (data != NULL);
|
|
|
|
|
|
|
|
|
|
if (G_IS_MEMORY_OUTPUT_STREAM (data))
|
|
|
|
|
{
|
|
|
|
|
GBytes *bytes = NULL;
|
|
|
|
|
GIcon *icon = NULL;
|
|
|
|
|
|
|
|
|
|
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (data));
|
|
|
|
|
icon = g_bytes_icon_new (g_steal_pointer (&bytes));
|
|
|
|
|
res = g_icon_serialize (icon),
|
|
|
|
|
|
|
|
|
|
g_bytes_unref (bytes);
|
|
|
|
|
g_clear_object (&icon);
|
|
|
|
|
}
|
|
|
|
|
else if (G_IS_UNIX_OUTPUT_STREAM (data))
|
|
|
|
|
{
|
|
|
|
|
int fd;
|
|
|
|
|
int fd_in;
|
|
|
|
|
|
|
|
|
|
fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (data));
|
|
|
|
|
g_assert (fd != -1);
|
|
|
|
|
|
|
|
|
|
if (lseek (fd, 0, SEEK_SET) == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno = errno;
|
|
|
|
|
|
|
|
|
|
g_task_return_new_error (task,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"lseek: %s", g_strerror (saved_errno));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fd_in = g_unix_fd_list_append (fd_list, fd, error);
|
|
|
|
|
if (fd_in == -1)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
res = g_variant_ref_sink (g_variant_new ("(sv)", "file-descriptor", g_variant_new_handle (fd_in)));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
res = g_variant_ref (g_task_get_task_data (task));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
serialize_icon_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *res,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
|
|
|
|
ParserData *data = g_task_get_task_data (task);
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GVariant *icon_out = NULL;
|
|
|
|
|
|
|
|
|
|
icon_out = serialize_icon_finish (res, data->fd_list, &error);
|
|
|
|
|
|
|
|
|
|
if (!icon_out)
|
|
|
|
|
{
|
|
|
|
|
g_prefix_error_literal (&error, "Failed to serialize icon: ");
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "icon", icon_out);
|
|
|
|
|
|
|
|
|
|
if (parser_data_release (data))
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&icon_out, g_variant_unref);
|
|
|
|
|
g_object_unref (task);
|
2024-11-05 18:51:29 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-11-27 15:01:23 +01:00
|
|
|
|
static void
|
|
|
|
|
file_read_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *res,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
|
|
|
|
GOutputStream *stream_out = G_OUTPUT_STREAM (g_task_get_task_data (task));
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GFileInputStream *stream_in = NULL;
|
|
|
|
|
|
|
|
|
|
stream_in = g_file_read_finish (G_FILE (source_object), res, &error);
|
|
|
|
|
if (!stream_in)
|
|
|
|
|
{
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_output_stream_splice_async (stream_out,
|
|
|
|
|
G_INPUT_STREAM (stream_in),
|
|
|
|
|
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
|
|
|
|
|
g_task_get_priority (task),
|
|
|
|
|
g_task_get_cancellable (task),
|
|
|
|
|
splice_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
|
|
|
|
|
g_object_unref (stream_in);
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
serialize_sound (GNotificationSound *sound,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = NULL;
|
|
|
|
|
GBytes *bytes = NULL;
|
|
|
|
|
GFile *file = NULL;
|
|
|
|
|
|
|
|
|
|
task = g_task_new (NULL, NULL, callback, user_data);
|
|
|
|
|
g_task_set_source_tag (task, serialize_sound);
|
|
|
|
|
|
|
|
|
|
if (sound == NULL)
|
|
|
|
|
{
|
|
|
|
|
g_task_set_task_data (task,
|
|
|
|
|
g_variant_take_ref (g_variant_new_string ("silent")),
|
|
|
|
|
(GDestroyNotify) g_variant_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
else if (g_notification_sound_is_default (sound))
|
|
|
|
|
{
|
|
|
|
|
g_task_set_task_data (task,
|
|
|
|
|
g_variant_take_ref (g_variant_new_string ("default")),
|
|
|
|
|
(GDestroyNotify) g_variant_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
else if ((bytes = g_notification_sound_get_bytes (sound)))
|
|
|
|
|
{
|
|
|
|
|
GOutputStream *stream_out = NULL;
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
fd = bytes_to_memfd ("notification-media",
|
|
|
|
|
bytes,
|
|
|
|
|
&error);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
{
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream_out = g_unix_output_stream_new (g_steal_fd (&fd), TRUE);
|
|
|
|
|
g_task_set_task_data (task, g_steal_pointer (&stream_out), g_object_unref);
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
}
|
|
|
|
|
else if ((file = g_notification_sound_get_file (sound)))
|
|
|
|
|
{
|
|
|
|
|
GOutputStream *stream_out = NULL;
|
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
|
|
fd = memfd_create ("notification-sound", MFD_ALLOW_SEALING);
|
|
|
|
|
if (fd == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno;
|
|
|
|
|
|
|
|
|
|
saved_errno = errno;
|
|
|
|
|
g_task_return_new_error (task,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"memfd_create: %s", g_strerror (saved_errno));
|
|
|
|
|
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream_out = g_unix_output_stream_new (g_steal_fd (&fd), TRUE);
|
|
|
|
|
g_task_set_task_data (task, g_steal_pointer (&stream_out), g_object_unref);
|
|
|
|
|
|
|
|
|
|
g_file_read_async (file,
|
|
|
|
|
g_task_get_priority (task),
|
|
|
|
|
NULL,
|
|
|
|
|
file_read_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_assert_not_reached ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GVariant*
|
|
|
|
|
serialize_sound_finish (GAsyncResult *result,
|
|
|
|
|
GUnixFDList *fd_list,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (result);
|
|
|
|
|
gpointer data;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (g_task_get_source_tag (task) == serialize_sound, NULL);
|
|
|
|
|
|
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (result), error))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
data = g_task_get_task_data (task);
|
|
|
|
|
g_assert (data != NULL);
|
|
|
|
|
|
|
|
|
|
if (G_IS_UNIX_OUTPUT_STREAM (data))
|
|
|
|
|
{
|
|
|
|
|
int fd;
|
|
|
|
|
int fd_in;
|
|
|
|
|
|
|
|
|
|
fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (data));
|
|
|
|
|
if (lseek (fd, 0, SEEK_SET) == -1)
|
|
|
|
|
{
|
|
|
|
|
int saved_errno = errno;
|
|
|
|
|
|
|
|
|
|
g_task_return_new_error (task,
|
|
|
|
|
G_IO_ERROR,
|
|
|
|
|
g_io_error_from_errno (saved_errno),
|
|
|
|
|
"lseek: %s", g_strerror (saved_errno));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fd_in = g_unix_fd_list_append (fd_list, fd, error);
|
|
|
|
|
if (fd_in == -1)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
return g_variant_ref_sink (g_variant_new ("(sv)", "file-descriptor", g_variant_new_handle (fd_in)));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_assert (data != NULL);
|
|
|
|
|
return g_variant_ref (data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
serialize_sound_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *res,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GTask *task = G_TASK (user_data);
|
|
|
|
|
ParserData *data = g_task_get_task_data (task);
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GVariant *sound_out = NULL;
|
|
|
|
|
|
|
|
|
|
sound_out = serialize_sound_finish (res, data->fd_list, &error);
|
|
|
|
|
|
|
|
|
|
if (!sound_out)
|
|
|
|
|
{
|
|
|
|
|
g_prefix_error_literal (&error, "Failed to serialize sound: ");
|
|
|
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "sound", sound_out);
|
|
|
|
|
|
|
|
|
|
if (parser_data_release (data))
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (&sound_out, g_variant_unref);
|
|
|
|
|
g_object_unref (task);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 12:50:46 +02:00
|
|
|
|
static GVariant *
|
2025-02-17 11:08:29 +01:00
|
|
|
|
serialize_buttons (GNotification *notification,
|
|
|
|
|
guint version)
|
2024-05-02 12:50:46 +02:00
|
|
|
|
{
|
|
|
|
|
GVariantBuilder builder;
|
|
|
|
|
guint n_buttons;
|
|
|
|
|
guint i;
|
2024-05-02 14:55:33 +02:00
|
|
|
|
const char *supported_purposes[] = {
|
|
|
|
|
G_NOTIFICATION_BUTTON_PURPOSE_CALL_ACCEPT,
|
|
|
|
|
G_NOTIFICATION_BUTTON_PURPOSE_CALL_DECLINE,
|
|
|
|
|
G_NOTIFICATION_BUTTON_PURPOSE_CALL_HANG_UP,
|
|
|
|
|
G_NOTIFICATION_BUTTON_PURPOSE_CALL_ENABLE_SPEAKERPHONE,
|
|
|
|
|
G_NOTIFICATION_BUTTON_PURPOSE_CALL_DISABLE_SPEAKERPHONE,
|
|
|
|
|
NULL
|
|
|
|
|
};
|
2025-02-17 11:08:29 +01:00
|
|
|
|
GNotificationSound *sound = NULL;
|
|
|
|
|
char *custom_sound_action = NULL;
|
|
|
|
|
GVariant *custom_sound_target = NULL;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
|
|
|
|
n_buttons = g_notification_get_n_buttons (notification);
|
|
|
|
|
|
2025-02-17 11:08:29 +01:00
|
|
|
|
sound = g_notification_get_sound (notification);
|
|
|
|
|
if (version > 1 && sound)
|
|
|
|
|
g_notification_sound_get_custom (sound, &custom_sound_action, &custom_sound_target);
|
|
|
|
|
|
|
|
|
|
if (n_buttons == 0 && custom_sound_action == NULL)
|
2024-05-02 12:50:46 +02:00
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
|
|
|
|
|
|
2025-02-17 11:08:29 +01:00
|
|
|
|
/* For the portal playing a custom sound is considered a button */
|
|
|
|
|
if (custom_sound_action)
|
|
|
|
|
{
|
|
|
|
|
g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
|
|
|
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "action", g_variant_new_take_string (custom_sound_action));
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "purpose", g_variant_new_string ("system.custom-alert"));
|
|
|
|
|
|
|
|
|
|
if (custom_sound_target)
|
|
|
|
|
{
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "target", custom_sound_target);
|
|
|
|
|
g_clear_pointer (&custom_sound_target, g_variant_unref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_builder_close (&builder);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 12:50:46 +02:00
|
|
|
|
for (i = 0; i < n_buttons; i++)
|
|
|
|
|
{
|
|
|
|
|
gchar *label;
|
2024-05-02 14:55:33 +02:00
|
|
|
|
gchar *purpose;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
gchar *action_name;
|
|
|
|
|
GVariant *target = NULL;
|
|
|
|
|
|
2024-05-02 14:55:33 +02:00
|
|
|
|
g_notification_get_button (notification, i, &label, &purpose, &action_name, &target);
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
|
|
|
|
g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
|
|
|
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "label", g_variant_new_take_string (label));
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "action", g_variant_new_take_string (action_name));
|
|
|
|
|
|
2024-05-02 14:55:33 +02:00
|
|
|
|
if (purpose && (g_strv_contains (supported_purposes, purpose) || g_str_has_prefix (purpose, "x-")))
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "purpose", g_variant_new_take_string (purpose));
|
|
|
|
|
|
2024-05-02 12:50:46 +02:00
|
|
|
|
if (target)
|
|
|
|
|
{
|
|
|
|
|
g_variant_builder_add (&builder, "{sv}", "target", target);
|
|
|
|
|
g_clear_pointer (&target, g_variant_unref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_builder_close (&builder);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 11:08:29 +01:00
|
|
|
|
return g_variant_builder_end (&builder);
|
2024-05-02 12:50:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GVariant *
|
|
|
|
|
serialize_priority (GNotification *notification)
|
|
|
|
|
{
|
|
|
|
|
GEnumClass *enum_class;
|
|
|
|
|
GEnumValue *value;
|
|
|
|
|
GVariant *nick;
|
|
|
|
|
|
|
|
|
|
enum_class = g_type_class_ref (G_TYPE_NOTIFICATION_PRIORITY);
|
|
|
|
|
value = g_enum_get_value (enum_class, g_notification_get_priority (notification));
|
|
|
|
|
g_assert (value != NULL);
|
|
|
|
|
nick = g_variant_new_string (value->value_nick);
|
|
|
|
|
g_type_class_unref (enum_class);
|
|
|
|
|
|
|
|
|
|
return nick;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 13:26:48 +02:00
|
|
|
|
static GVariant *
|
|
|
|
|
serialize_display_hint (GNotification *notification)
|
|
|
|
|
{
|
|
|
|
|
GFlagsClass *flags_class = NULL;
|
|
|
|
|
GFlagsValue *flags_value;
|
|
|
|
|
GVariantBuilder builder;
|
|
|
|
|
GNotificationDisplayHintFlags display_hint;
|
|
|
|
|
gboolean should_show_as_new = TRUE;
|
|
|
|
|
|
|
|
|
|
display_hint = g_notification_get_display_hint_flags (notification);
|
|
|
|
|
|
|
|
|
|
/* If the only flag is to update the notification we don't need to set any display hints */
|
|
|
|
|
if (display_hint == G_NOTIFICATION_DISPLAY_HINT_UPDATE)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
flags_class = g_type_class_ref (G_TYPE_NOTIFICATION_DISPLAY_HINT_FLAGS);
|
|
|
|
|
|
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
|
|
|
|
|
|
|
|
|
while (display_hint != G_NOTIFICATION_DISPLAY_HINT_NONE &&
|
|
|
|
|
(flags_value = g_flags_get_first_value (flags_class, display_hint)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* The display-hint 'update' needs to be serialized as 'show-as-new'
|
|
|
|
|
* because we have the opposite default than the portal */
|
|
|
|
|
if (flags_value->value == G_NOTIFICATION_DISPLAY_HINT_UPDATE)
|
|
|
|
|
should_show_as_new = FALSE;
|
|
|
|
|
else
|
|
|
|
|
g_variant_builder_add (&builder, "s", flags_value->value_nick);
|
|
|
|
|
display_hint &= ~flags_value->value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (should_show_as_new)
|
|
|
|
|
g_variant_builder_add (&builder, "s", "show-as-new");
|
|
|
|
|
|
|
|
|
|
g_type_class_unref (flags_class);
|
|
|
|
|
return g_variant_builder_end (&builder);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 14:49:22 +02:00
|
|
|
|
static GVariant *
|
|
|
|
|
serialize_category (GNotification *notification)
|
|
|
|
|
{
|
|
|
|
|
const char *category;
|
|
|
|
|
const char *supported_categories[] = {
|
|
|
|
|
G_NOTIFICATION_CATEGORY_IM_RECEIVED,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_ALARM_RINGING,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CALL_INCOMING,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CALL_OUTGOING,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CALL_UNANSWERED,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_WEATHER_WARNING_EXTREME,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CELLBROADCAST_DANGER_SEVERE,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CELLBROADCAST_AMBER_ALERT,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_CELLBROADCAST_TEST,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_OS_BATTERY_LOW,
|
|
|
|
|
G_NOTIFICATION_CATEGORY_BROWSER_WEB_NOTIFICATION,
|
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
category = g_notification_get_category (notification);
|
|
|
|
|
|
|
|
|
|
/* The portal fails if we give categories that aren't supported that
|
|
|
|
|
* don't start with the x-vendor prefix prefix */
|
|
|
|
|
if (category && (g_strv_contains (supported_categories, category) || g_str_has_prefix (category, "x-")))
|
|
|
|
|
return g_variant_new_string (category);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
static void
|
|
|
|
|
serialize_notification (const char *id,
|
|
|
|
|
GNotification *notification,
|
|
|
|
|
uint version,
|
|
|
|
|
GAsyncReadyCallback callback,
|
|
|
|
|
gpointer user_data)
|
2024-05-02 12:50:46 +02:00
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
ParserData *data;
|
|
|
|
|
GTask *task;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
const gchar *body;
|
2024-05-02 13:31:50 +02:00
|
|
|
|
const gchar *markup_body;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
GIcon *icon;
|
2024-05-02 13:26:48 +02:00
|
|
|
|
GVariant *display_hint = NULL;
|
2024-05-02 14:49:22 +02:00
|
|
|
|
GVariant *category;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
gchar *default_action = NULL;
|
|
|
|
|
GVariant *default_action_target = NULL;
|
|
|
|
|
GVariant *buttons = NULL;
|
2024-11-27 15:01:23 +01:00
|
|
|
|
GNotificationSound *sound = NULL;
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
task = g_task_new (NULL, NULL, callback, user_data);
|
|
|
|
|
g_task_set_source_tag (task, serialize_notification);
|
|
|
|
|
|
|
|
|
|
data = parser_data_new (id);
|
|
|
|
|
g_task_set_task_data (task,
|
|
|
|
|
data,
|
|
|
|
|
parser_data_free);
|
|
|
|
|
parser_data_hold (data);
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "title", g_variant_new_string (g_notification_get_title (notification)));
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
2024-05-02 13:31:50 +02:00
|
|
|
|
/* Prefer the body with markup over the body */
|
|
|
|
|
if (version > 1 && (markup_body = g_notification_get_body_with_markup (notification)))
|
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "markup-body", g_variant_new_string (markup_body));
|
|
|
|
|
else if ((body = g_notification_get_body (notification)))
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "body", g_variant_new_string (body));
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
|
|
|
|
if ((icon = g_notification_get_icon (notification)))
|
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
if (G_IS_THEMED_ICON (icon) || G_IS_BYTES_ICON (icon) || G_IS_LOADABLE_ICON (icon))
|
2024-05-02 12:50:46 +02:00
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
parser_data_hold (data);
|
|
|
|
|
serialize_icon (icon,
|
|
|
|
|
version,
|
|
|
|
|
serialize_icon_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
g_warning ("Can’t add icon to portal notification: %s isn’t handled", g_type_name_from_instance ((GTypeInstance *)icon));
|
2024-05-02 12:50:46 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-27 15:01:23 +01:00
|
|
|
|
sound = g_notification_get_sound (notification);
|
|
|
|
|
/* For the portal a custom sound is considered a button */
|
|
|
|
|
if (version > 1 && !(sound && g_notification_sound_get_custom (sound, NULL, NULL)))
|
|
|
|
|
{
|
|
|
|
|
parser_data_hold (data);
|
|
|
|
|
serialize_sound (sound,
|
|
|
|
|
serialize_sound_cb,
|
|
|
|
|
g_object_ref (task));
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "priority", serialize_priority (notification));
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
2024-05-02 13:26:48 +02:00
|
|
|
|
if ((display_hint = serialize_display_hint (notification)))
|
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "display-hint", display_hint);
|
|
|
|
|
|
2024-05-02 14:49:22 +02:00
|
|
|
|
if ((category = serialize_category (notification)))
|
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "category", category);
|
|
|
|
|
|
2024-05-02 12:50:46 +02:00
|
|
|
|
if (g_notification_get_default_action (notification, &default_action, &default_action_target))
|
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "default-action",
|
2024-05-02 12:50:46 +02:00
|
|
|
|
g_variant_new_take_string (g_steal_pointer (&default_action)));
|
|
|
|
|
|
|
|
|
|
if (default_action_target)
|
|
|
|
|
{
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "default-action-target",
|
2024-05-02 12:50:46 +02:00
|
|
|
|
default_action_target);
|
|
|
|
|
g_clear_pointer (&default_action_target, g_variant_unref);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 11:08:29 +01:00
|
|
|
|
if ((buttons = serialize_buttons (notification, version)))
|
2024-11-18 16:25:17 +01:00
|
|
|
|
g_variant_builder_add (data->builder, "{sv}", "buttons", buttons);
|
2024-05-02 12:50:46 +02:00
|
|
|
|
|
2024-11-18 16:25:17 +01:00
|
|
|
|
if (parser_data_release (data))
|
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
|
|
|
|
|
|
g_clear_object (&task);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static GVariant *
|
|
|
|
|
serialize_notification_finish (GAsyncResult *result,
|
|
|
|
|
GUnixFDList **fd_list,
|
|
|
|
|
GError **error)
|
|
|
|
|
{
|
|
|
|
|
ParserData *data;
|
|
|
|
|
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == serialize_notification, NULL);
|
|
|
|
|
|
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (result), error))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
data = g_task_get_task_data (G_TASK (result));
|
|
|
|
|
|
|
|
|
|
if (fd_list)
|
|
|
|
|
*fd_list = g_steal_pointer (&data->fd_list);
|
|
|
|
|
|
|
|
|
|
return g_variant_ref_sink (g_variant_builder_end (g_steal_pointer (&data->builder)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
serialize_notification_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GNotificationBackend *backend = G_NOTIFICATION_BACKEND (user_data);
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GUnixFDList *fd_list = NULL;
|
|
|
|
|
GVariant *parameters = NULL;
|
|
|
|
|
|
|
|
|
|
parameters = serialize_notification_finish (result, &fd_list, &error);
|
|
|
|
|
|
|
|
|
|
if (!parameters)
|
|
|
|
|
{
|
|
|
|
|
g_warning ("Failed to send notification: %s", error->message);
|
|
|
|
|
g_error_free (error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_dbus_connection_call_with_unix_fd_list (backend->dbus_connection,
|
|
|
|
|
"org.freedesktop.portal.Desktop",
|
|
|
|
|
"/org/freedesktop/portal/desktop",
|
|
|
|
|
"org.freedesktop.portal.Notification",
|
|
|
|
|
"AddNotification",
|
|
|
|
|
parameters,
|
|
|
|
|
NULL,
|
|
|
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
|
|
|
-1,
|
|
|
|
|
fd_list,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
g_object_unref (backend);
|
|
|
|
|
g_object_unref (fd_list);
|
|
|
|
|
g_variant_unref (parameters);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
get_supported_features_cb (GObject *source_object,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
GPortalNotificationBackend *backend = G_PORTAL_NOTIFICATION_BACKEND (source_object);
|
|
|
|
|
CallData *data = user_data;
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
|
|
if (!get_supported_features_finish (backend, result, &error))
|
|
|
|
|
{
|
|
|
|
|
g_warning ("Failed to get notification portal version: %s", error->message);
|
|
|
|
|
g_clear_pointer (&error, g_error_free);
|
|
|
|
|
call_data_free (data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serialize_notification (data->id,
|
|
|
|
|
data->notification,
|
|
|
|
|
backend->version,
|
|
|
|
|
serialize_notification_cb,
|
|
|
|
|
g_object_ref (backend));
|
|
|
|
|
|
|
|
|
|
call_data_free (data);
|
2024-05-02 12:50:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-01 01:12:30 -04:00
|
|
|
|
static gboolean
|
|
|
|
|
g_portal_notification_backend_is_supported (void)
|
|
|
|
|
{
|
|
|
|
|
return glib_should_use_portal ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_portal_notification_backend_send_notification (GNotificationBackend *backend,
|
|
|
|
|
const gchar *id,
|
|
|
|
|
GNotification *notification)
|
|
|
|
|
{
|
2024-11-05 18:51:29 +01:00
|
|
|
|
get_supported_features (G_PORTAL_NOTIFICATION_BACKEND (backend),
|
|
|
|
|
get_supported_features_cb,
|
2024-11-18 16:25:17 +01:00
|
|
|
|
call_data_new (id, notification));
|
2016-07-01 01:12:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_portal_notification_backend_withdraw_notification (GNotificationBackend *backend,
|
|
|
|
|
const gchar *id)
|
|
|
|
|
{
|
|
|
|
|
g_dbus_connection_call (backend->dbus_connection,
|
|
|
|
|
"org.freedesktop.portal.Desktop",
|
|
|
|
|
"/org/freedesktop/portal/desktop",
|
|
|
|
|
"org.freedesktop.portal.Notification",
|
|
|
|
|
"RemoveNotification",
|
|
|
|
|
g_variant_new ("(s)", id),
|
|
|
|
|
G_VARIANT_TYPE_UNIT,
|
|
|
|
|
G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_portal_notification_backend_init (GPortalNotificationBackend *backend)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
g_portal_notification_backend_class_init (GPortalNotificationBackendClass *class)
|
|
|
|
|
{
|
|
|
|
|
GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (class);
|
|
|
|
|
|
|
|
|
|
backend_class->is_supported = g_portal_notification_backend_is_supported;
|
|
|
|
|
backend_class->send_notification = g_portal_notification_backend_send_notification;
|
|
|
|
|
backend_class->withdraw_notification = g_portal_notification_backend_withdraw_notification;
|
|
|
|
|
}
|