libnotify/libnotify/notification.c

1472 lines
53 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2006 Christian Hammond
* Copyright (C) 2006 John Palmieri
* Copyright (C) 2010 Red Hat, Inc.
* Copyright © 2010 Christian Persch
*
* 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
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <gio/gio.h>
#include "notify.h"
#include "internal.h"
/**
* SECTION:notification
* @Short_description: A passive pop-up notification.
* @Title: NotifyNotification
*
* #NotifyNotification represents a passive pop-up notification. It can
* contain summary text, body text, and an icon, as well as hints specifying
* how the notification should be presented. The notification is rendered
* by a notification daemon, and may present the notification in any number
* of ways. As such, there is a clear separation of content and presentation,
* and this API enforces that.
*/
#if !defined(G_PARAM_STATIC_NAME) && !defined(G_PARAM_STATIC_NICK) && \
!defined(G_PARAM_STATIC_BLURB)
# define G_PARAM_STATIC_NAME 0
# define G_PARAM_STATIC_NICK 0
# define G_PARAM_STATIC_BLURB 0
#endif
static void notify_notification_class_init (NotifyNotificationClass *klass);
static void notify_notification_init (NotifyNotification *sp);
static void notify_notification_finalize (GObject *object);
typedef struct
{
NotifyActionCallback cb;
GFreeFunc free_func;
gpointer user_data;
} CallbackPair;
struct _NotifyNotificationPrivate
{
guint32 id;
char *app_name;
char *summary;
char *body;
char *activation_token;
const char *snap_path;
const char *snap_name;
char *snap_app;
/* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name;
/*
* -1 = use server default
* 0 = never timeout
* > 0 = Number of milliseconds before we timeout
*/
gint timeout;
GSList *actions;
GHashTable *action_map;
GHashTable *hints;
gboolean has_nondefault_actions;
gboolean activating;
gboolean updates_pending;
gulong proxy_signal_handler;
gint closed_reason;
};
enum
{
SIGNAL_CLOSED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_ID,
PROP_APP_NAME,
PROP_SUMMARY,
PROP_BODY,
PROP_ICON_NAME,
PROP_CLOSED_REASON
};
static void notify_notification_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void notify_notification_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static guint signals[LAST_SIGNAL] = { 0 };
static GObjectClass *parent_class = NULL;
G_DEFINE_TYPE (NotifyNotification, notify_notification, G_TYPE_OBJECT)
static GObject *
notify_notification_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_params)
{
GObject *object;
object = parent_class->constructor (type,
n_construct_properties,
construct_params);
_notify_cache_add_notification (NOTIFY_NOTIFICATION (object));
return object;
}
static void
notify_notification_class_init (NotifyNotificationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
object_class->constructor = notify_notification_constructor;
object_class->get_property = notify_notification_get_property;
object_class->set_property = notify_notification_set_property;
object_class->finalize = notify_notification_finalize;
/**
* NotifyNotification::closed:
* @notification: The object which received the signal.
*
* Emitted when the notification is closed.
*/
signals[SIGNAL_CLOSED] =
g_signal_new ("closed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NotifyNotificationClass, closed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
g_object_class_install_property (object_class,
PROP_ID,
g_param_spec_int ("id", "ID",
"The notification ID",
0,
G_MAXINT32,
0,
G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_APP_NAME,
g_param_spec_string ("app-name",
"Application name",
"The application name to use for this notification",
NULL,
G_PARAM_READWRITE
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_SUMMARY,
g_param_spec_string ("summary",
"Summary",
"The summary text",
NULL,
G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_BODY,
g_param_spec_string ("body",
"Message Body",
"The message body text",
NULL,
G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_ICON_NAME,
g_param_spec_string ("icon-name",
"Icon Name",
"The icon filename or icon theme-compliant name",
NULL,
G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
g_object_class_install_property (object_class,
PROP_CLOSED_REASON,
g_param_spec_int ("closed-reason",
"Closed Reason",
"The reason code for why the notification was closed",
-1,
G_MAXINT32,
-1,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
| G_PARAM_STATIC_BLURB));
}
static void
notify_notification_update_internal (NotifyNotification *notification,
const char *app_name,
const char *summary,
const char *body,
const char *icon);
static void
notify_notification_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NotifyNotification *notification = NOTIFY_NOTIFICATION (object);
NotifyNotificationPrivate *priv = notification->priv;
switch (prop_id) {
case PROP_ID:
priv->id = g_value_get_int (value);
break;
case PROP_APP_NAME:
notify_notification_update_internal (notification,
g_value_get_string (value),
priv->summary,
priv->body,
priv->icon_name);
break;
case PROP_SUMMARY:
notify_notification_update_internal (notification,
priv->app_name,
g_value_get_string (value),
priv->body,
priv->icon_name);
break;
case PROP_BODY:
notify_notification_update_internal (notification,
priv->app_name,
priv->summary,
g_value_get_string (value),
priv->icon_name);
break;
case PROP_ICON_NAME:
notify_notification_update_internal (notification,
priv->app_name,
priv->summary,
priv->body,
g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
notify_notification_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
NotifyNotification *notification = NOTIFY_NOTIFICATION (object);
NotifyNotificationPrivate *priv = notification->priv;
switch (prop_id) {
case PROP_ID:
g_value_set_int (value, priv->id);
break;
case PROP_SUMMARY:
g_value_set_string (value, priv->summary);
break;
case PROP_APP_NAME:
g_value_set_string (value, priv->app_name);
break;
case PROP_BODY:
g_value_set_string (value, priv->body);
break;
case PROP_ICON_NAME:
g_value_set_string (value, priv->icon_name);
break;
case PROP_CLOSED_REASON:
g_value_set_int (value, priv->closed_reason);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
destroy_pair (CallbackPair *pair)
{
if (pair->user_data != NULL && pair->free_func != NULL) {
pair->free_func (pair->user_data);
}
g_free (pair);
}
static void
maybe_initialize_snap (NotifyNotification *obj)
{
NotifyNotificationPrivate *priv = obj->priv;
gchar *cgroup_contents = NULL;
priv->snap_path = g_getenv ("SNAP");
if (priv->snap_path == NULL)
return;
if (*priv->snap_path == '\0' ||
!strchr (priv->snap_path, G_DIR_SEPARATOR)) {
priv->snap_path = NULL;
return;
}
priv->snap_name = g_getenv ("SNAP_NAME");
if (priv->snap_name && *priv->snap_name == '\0') {
priv->snap_name = NULL;
}
if (g_file_get_contents ("/proc/self/cgroup", &cgroup_contents,
NULL, NULL)) {
gchar **lines = g_strsplit (cgroup_contents, "\n", -1);
gchar *found_snap_name = NULL;
gint i;
for (i = 0; lines[i]; ++i) {
gchar **parts = g_strsplit (lines[i], ":", 3);
gchar *basename;
gchar **ns;
guint ns_length;
if (g_strv_length (parts) != 3) {
g_strfreev (parts);
continue;
}
basename = g_path_get_basename (parts[2]);
g_strfreev (parts);
if (!basename) {
continue;
}
ns = g_strsplit (basename, ".", -1);
ns_length = g_strv_length (ns);
g_free (basename);
if (ns_length < 2 || !g_str_equal (ns[0], "snap")) {
g_strfreev (ns);
continue;
}
if (priv->snap_name == NULL) {
g_free (found_snap_name);
found_snap_name = g_strdup (ns[1]);
}
if (ns_length < 3) {
g_strfreev (ns);
continue;
}
if (priv->snap_name == NULL) {
priv->snap_name = found_snap_name;
found_snap_name = NULL;
}
if (g_str_equal (ns[1], priv->snap_name)) {
priv->snap_app = g_strdup (ns[2]);
g_strfreev (ns);
break;
}
g_strfreev (ns);
}
if (priv->snap_name == NULL && found_snap_name != NULL) {
priv->snap_name = found_snap_name;
found_snap_name = NULL;
}
g_strfreev (lines);
g_free (found_snap_name);
}
if (priv->snap_app == NULL) {
priv->snap_app = g_strdup (priv->snap_name);
}
g_debug ("SNAP path: %s", priv->snap_path);
g_debug ("SNAP name: %s", priv->snap_name);
g_debug ("SNAP app: %s", priv->snap_app);
g_free (cgroup_contents);
}
static void
notify_notification_init (NotifyNotification *obj)
{
obj->priv = g_new0 (NotifyNotificationPrivate, 1);
obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
obj->priv->closed_reason = -1;
obj->priv->hints = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) g_variant_unref);
obj->priv->action_map = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) destroy_pair);
maybe_initialize_snap (obj);
}
static void
notify_notification_finalize (GObject *object)
{
NotifyNotification *obj = NOTIFY_NOTIFICATION (object);
NotifyNotificationPrivate *priv = obj->priv;
GDBusProxy *proxy;
_notify_cache_remove_notification (obj);
g_free (priv->app_name);
g_free (priv->summary);
g_free (priv->body);
g_free (priv->icon_name);
g_free (priv->activation_token);
g_free (priv->snap_app);
if (priv->actions != NULL) {
g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
g_slist_free (priv->actions);
}
if (priv->action_map != NULL)
g_hash_table_destroy (priv->action_map);
if (priv->hints != NULL)
g_hash_table_destroy (priv->hints);
proxy = _notify_get_proxy (NULL);
if (proxy != NULL && priv->proxy_signal_handler != 0) {
g_signal_handler_disconnect (proxy, priv->proxy_signal_handler);
}
g_free (obj->priv);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* notify_notification_new:
* @summary: The required summary text.
* @body: (allow-none): The optional body text.
* @icon: (allow-none): The optional icon theme icon name or filename.
*
* Creates a new #NotifyNotification. The summary text is required, but
* all other parameters are optional.
*
* Returns: The new #NotifyNotification.
*/
NotifyNotification *
notify_notification_new (const char *summary,
const char *body,
const char *icon)
{
return g_object_new (NOTIFY_TYPE_NOTIFICATION,
"summary", summary,
"body", body,
"icon-name", icon,
NULL);
}
static gchar *
try_prepend_path (const char *base_path,
const char *path)
{
gchar *path_filename;
gchar *path_ret;
gboolean was_uri;
if (!path || *path == '\0')
return NULL;
was_uri = TRUE;
path_ret = NULL;
path_filename = g_filename_from_uri (base_path, NULL, NULL);
if (path_filename == NULL) {
was_uri = FALSE;
if (base_path && base_path[0] == G_DIR_SEPARATOR) {
path_filename = g_strdup (base_path);
} else {
path_filename = realpath (base_path, NULL);
if (path_filename == NULL) {
/* File path is not existing, but let's check
* if it's under the base path before giving up
*/
path_filename = g_strdup (base_path);
}
}
}
if (g_str_has_prefix (path_filename, path)) {
path_ret = g_strdup (path_filename);
} else {
g_debug ("Trying to look at file '%s' in the '%s' prefix.",
base_path,
path);
path_ret = g_build_filename (path, path_filename, NULL);
}
if (!g_file_test (path_ret, G_FILE_TEST_EXISTS)) {
g_debug ("Nothing found at %s", path_ret);
g_free (path_ret);
path_ret = NULL;
} else if (was_uri) {
gchar *uri = g_filename_to_uri (path_ret, NULL, NULL);
if (uri != NULL) {
g_free (path_ret);
path_ret = uri;
}
}
g_free (path_filename);
return path_ret;
}
static gchar *
try_prepend_snap_desktop (NotifyNotification *notification,
const gchar *desktop)
{
NotifyNotificationPrivate *priv = notification->priv;
gchar *ret = NULL;
/*
* if it's an absolute path, try prepending $SNAP, otherwise try
* ${SNAP_NAME}_; snap .desktop files are in the format
* ${SNAP_NAME}_desktop_file_name
*/
ret = try_prepend_path (desktop, priv->snap_path);
if (ret == NULL && priv->snap_name != NULL &&
strchr (desktop, G_DIR_SEPARATOR) == NULL) {
ret = g_strdup_printf ("%s_%s", priv->snap_name, desktop);
}
return ret;
}
static gchar *
try_prepend_snap (NotifyNotification *notification,
const gchar *value)
{
/* hardcoded paths to icons might be relocated under $SNAP */
return try_prepend_path (value, notification->priv->snap_path);
}
static void
notify_notification_update_internal (NotifyNotification *notification,
const char *app_name,
const char *summary,
const char *body,
const char *icon)
{
if (notification->priv->app_name != app_name) {
g_free (notification->priv->app_name);
notification->priv->app_name = g_strdup (app_name);
g_object_notify (G_OBJECT (notification), "app-name");
}
if (notification->priv->summary != summary) {
g_free (notification->priv->summary);
notification->priv->summary = g_strdup (summary);
g_object_notify (G_OBJECT (notification), "summary");
}
if (notification->priv->body != body) {
g_free (notification->priv->body);
notification->priv->body = (body != NULL
&& *body != '\0' ? g_strdup (body) : NULL);
g_object_notify (G_OBJECT (notification), "body");
}
if (notification->priv->icon_name != icon) {
gchar *snapped_icon;
g_free (notification->priv->icon_name);
notification->priv->icon_name = (icon != NULL
&& *icon != '\0' ? g_strdup (icon) : NULL);
snapped_icon = try_prepend_snap_desktop (notification,
notification->priv->icon_name);
if (snapped_icon != NULL) {
g_debug ("Icon updated in snap environment: '%s' -> '%s'\n",
notification->priv->icon_name, snapped_icon);
g_free (notification->priv->icon_name);
notification->priv->icon_name = snapped_icon;
}
g_object_notify (G_OBJECT (notification), "icon-name");
}
notification->priv->updates_pending = TRUE;
}
/**
* notify_notification_update:
* @notification: The notification to update.
* @summary: The new required summary text.
* @body: (allow-none): The optional body text.
* @icon: (allow-none): The optional icon theme icon name or filename.
*
* Updates the notification text and icon. This won't send the update out
* and display it on the screen. For that, you will need to call
* notify_notification_show().
*
* Returns: %TRUE, unless an invalid parameter was passed.
*/
gboolean
notify_notification_update (NotifyNotification *notification,
const char *summary,
const char *body,
const char *icon)
{
g_return_val_if_fail (notification != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
g_return_val_if_fail (summary != NULL && *summary != '\0', FALSE);
notify_notification_update_internal (notification,
notification->priv->app_name,
summary, body, icon);
return TRUE;
}
static void
proxy_g_signal_cb (GDBusProxy *proxy,
const char *sender_name,
const char *signal_name,
GVariant *parameters,
NotifyNotification *notification)
{
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
guint32 id, reason;
g_variant_get (parameters, "(uu)", &id, &reason);
if (id != notification->priv->id)
return;
g_object_ref (G_OBJECT (notification));
notification->priv->closed_reason = reason;
g_signal_emit (notification, signals[SIGNAL_CLOSED], 0);
notification->priv->id = 0;
g_object_unref (G_OBJECT (notification));
} else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
guint32 id;
const char *action;
CallbackPair *pair;
g_variant_get (parameters, "(u&s)", &id, &action);
if (id != notification->priv->id)
return;
pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map,
action);
if (pair == NULL) {
if (g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action);
}
} else {
notification->priv->activating = TRUE;
pair->cb (notification, (char *) action, pair->user_data);
notification->priv->activating = FALSE;
g_free (notification->priv->activation_token);
notification->priv->activation_token = NULL;
}
} else if (g_strcmp0 (signal_name, "ActivationToken") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
guint32 id;
const char *activation_token;
g_variant_get (parameters, "(u&s)", &id, &activation_token);
if (id != notification->priv->id)
return;
g_free (notification->priv->activation_token);
notification->priv->activation_token = g_strdup (activation_token);
}
}
/**
* notify_notification_show:
* @notification: The notification.
* @error: The returned error information.
*
* Tells the notification server to display the notification on the screen.
*
* Returns: %TRUE if successful. On error, this will return %FALSE and set
* @error.
*/
gboolean
notify_notification_show (NotifyNotification *notification,
GError **error)
{
NotifyNotificationPrivate *priv;
GDBusProxy *proxy;
GVariantBuilder actions_builder, hints_builder;
GSList *l;
GHashTableIter iter;
gpointer key, data;
GVariant *result;
#ifdef GLIB_VERSION_2_32
GApplication *application = NULL;
#endif
g_return_val_if_fail (notification != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!notify_is_initted ()) {
g_warning ("you must call notify_init() before showing");
g_assert_not_reached ();
}
priv = notification->priv;
proxy = _notify_get_proxy (error);
if (proxy == NULL) {
return FALSE;
}
if (priv->proxy_signal_handler == 0) {
priv->proxy_signal_handler = g_signal_connect (proxy,
"g-signal",
G_CALLBACK (proxy_g_signal_cb),
notification);
}
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
for (l = priv->actions; l != NULL; l = l->next) {
g_variant_builder_add (&actions_builder, "s", l->data);
}
g_variant_builder_init (&hints_builder, G_VARIANT_TYPE ("a{sv}"));
g_hash_table_iter_init (&iter, priv->hints);
while (g_hash_table_iter_next (&iter, &key, &data)) {
g_variant_builder_add (&hints_builder, "{sv}", key, data);
}
if (g_hash_table_lookup (priv->hints, "sender-pid") == NULL) {
g_variant_builder_add (&hints_builder, "{sv}", "sender-pid",
g_variant_new_int64 (getpid ()));
}
if (priv->snap_app &&
g_hash_table_lookup (priv->hints, "desktop-entry") == NULL) {
gchar *snap_desktop;
snap_desktop = g_strdup_printf ("%s_%s",
priv->snap_name,
priv->snap_app);
g_debug ("Using desktop entry: %s", snap_desktop);
g_variant_builder_add (&hints_builder, "{sv}",
"desktop-entry",
g_variant_new_take_string (snap_desktop));
}
#ifdef GLIB_VERSION_2_32
if (!priv->snap_app) {
application = g_application_get_default ();
}
if (application != NULL) {
GVariant *desktop_entry = g_hash_table_lookup (priv->hints, "desktop-entry");
if (desktop_entry == NULL) {
const char *application_id = g_application_get_application_id (application);
g_debug ("Using desktop entry: %s", application_id);
g_variant_builder_add (&hints_builder, "{sv}", "desktop-entry",
g_variant_new_string (application_id));
}
}
#endif
/* TODO: make this nonblocking */
result = g_dbus_proxy_call_sync (proxy,
"Notify",
g_variant_new ("(susssasa{sv}i)",
priv->app_name ? priv->app_name : notify_get_app_name (),
priv->id,
priv->icon_name ? priv->icon_name : "",
priv->summary ? priv->summary : "",
priv->body ? priv->body : "",
&actions_builder,
&hints_builder,
priv->timeout),
G_DBUS_CALL_FLAGS_NONE,
-1 /* FIXME ? */,
NULL,
error);
if (result == NULL) {
return FALSE;
}
if (!g_variant_is_of_type (result, G_VARIANT_TYPE ("(u)"))) {
g_variant_unref (result);
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"Unexpected reply type");
return FALSE;
}
g_variant_get (result, "(u)", &priv->id);
g_variant_unref (result);
return TRUE;
}
/**
* notify_notification_set_timeout:
* @notification: The notification.
* @timeout: The timeout in milliseconds.
*
* Sets the timeout of the notification. To set the default time, pass
* %NOTIFY_EXPIRES_DEFAULT as @timeout. To set the notification to never
* expire, pass %NOTIFY_EXPIRES_NEVER.
*
* Note that the timeout may be ignored by the server.
*/
void
notify_notification_set_timeout (NotifyNotification *notification,
gint timeout)
{
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
notification->priv->timeout = timeout;
}
gint
_notify_notification_get_timeout (const NotifyNotification *notification)
{
g_return_val_if_fail (notification != NULL, -1);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
return notification->priv->timeout;
}
/**
* notify_notification_set_category:
* @notification: The notification.
* @category: The category.
*
* Sets the category of this notification. This can be used by the
* notification server to filter or display the data in a certain way.
*/
void
notify_notification_set_category (NotifyNotification *notification,
const char *category)
{
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (category != NULL && category[0] != '\0') {
notify_notification_set_hint_string (notification,
"category",
category);
}
}
/**
* notify_notification_set_urgency:
* @notification: The notification.
* @urgency: The urgency level.
*
* Sets the urgency level of this notification.
*
* See: #NotifyUrgency
*/
void
notify_notification_set_urgency (NotifyNotification *notification,
NotifyUrgency urgency)
{
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
notify_notification_set_hint_byte (notification,
"urgency",
(guchar) urgency);
}
/**
* notify_notification_set_icon_from_pixbuf:
* @notification: The notification.
* @icon: The icon.
*
* Sets the icon in the notification from a #GdkPixbuf.
* Deprecated: use notify_notification_set_image_from_pixbuf() instead.
*
*/
void
notify_notification_set_icon_from_pixbuf (NotifyNotification *notification,
GdkPixbuf *icon)
{
notify_notification_set_image_from_pixbuf (notification, icon);
}
/**
* notify_notification_set_image_from_pixbuf:
* @notification: The notification.
* @pixbuf: The image.
*
* Sets the image in the notification from a #GdkPixbuf.
*
*/
void
notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
GdkPixbuf *pixbuf)
{
gint width;
gint height;
gint rowstride;
gint bits_per_sample;
gint n_channels;
guchar *image;
gboolean has_alpha;
gsize image_len;
GVariant *value;
const char *hint_name;
g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf));
if (_notify_check_spec_version(1, 2)) {
hint_name = "image-data";
} else if (_notify_check_spec_version(1, 1)) {
hint_name = "image_data";
} else {
hint_name = "icon_data";
}
if (pixbuf == NULL) {
notify_notification_set_hint (notification, hint_name, NULL);
return;
}
g_object_get (pixbuf,
"width", &width,
"height", &height,
"rowstride", &rowstride,
"n-channels", &n_channels,
"bits-per-sample", &bits_per_sample,
"pixels", &image,
"has-alpha", &has_alpha,
NULL);
image_len = (height - 1) * rowstride + width *
((n_channels * bits_per_sample + 7) / 8);
value = g_variant_new ("(iiibii@ay)",
width,
height,
rowstride,
has_alpha,
bits_per_sample,
n_channels,
g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
image,
image_len,
TRUE,
(GDestroyNotify) g_object_unref,
g_object_ref (pixbuf)));
notify_notification_set_hint (notification, hint_name, value);
}
typedef gchar * (*StringParserFunc) (NotifyNotification *, const gchar *);
static GVariant *
get_parsed_variant (NotifyNotification *notification,
const char *key,
GVariant *variant,
StringParserFunc str_parser)
{
const char *str = g_variant_get_string (variant, NULL);
gchar *parsed = str_parser (notification, str);
if (parsed != NULL && g_strcmp0 (str, parsed) != 0) {
g_debug ("Hint %s updated in snap environment: '%s' -> '%s'\n",
key, str, parsed);
g_variant_unref (variant);
variant = g_variant_new_take_string (parsed);
}
return variant;
}
static GVariant *
maybe_parse_snap_hint_value (NotifyNotification *notification,
const gchar *key,
GVariant *value)
{
StringParserFunc parse_func = NULL;
if (!notification->priv->snap_path)
return value;
if (g_strcmp0 (key, "desktop-entry") == 0) {
parse_func = try_prepend_snap_desktop;
} else if (g_strcmp0 (key, "image-path") == 0 ||
g_strcmp0 (key, "image_path") == 0 ||
g_strcmp0 (key, "sound-file") == 0) {
parse_func = try_prepend_snap;
}
if (parse_func == NULL) {
return value;
}
return get_parsed_variant (notification, key, value, parse_func);
}
/**
* notify_notification_set_hint:
* @notification: a #NotifyNotification
* @key: the hint key
* @value: (allow-none): the hint value, or %NULL to unset the hint
*
* Sets a hint for @key with value @value. If @value is %NULL,
* a previously set hint for @key is unset.
*
* If @value is floating, it is consumed.
*
* Since: 0.6
*/
void
notify_notification_set_hint (NotifyNotification *notification,
const char *key,
GVariant *value)
{
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
g_return_if_fail (key != NULL && *key != '\0');
if (value != NULL) {
value = maybe_parse_snap_hint_value (notification, key, value);
g_hash_table_insert (notification->priv->hints,
g_strdup (key),
g_variant_ref_sink (value));
} else {
g_hash_table_remove (notification->priv->hints, key);
}
}
/**
* notify_notification_set_app_name:
* @notification: a #NotifyNotification
* @app_name: the localised application name
*
* Sets the application name for the notification. If this function is
* not called or if @app_name is %NULL, the application name will be
* set from the value used in notify_init() or overridden with
* notify_set_app_name().
*
* Since: 0.7.3
*/
void
notify_notification_set_app_name (NotifyNotification *notification,
const char *app_name)
{
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
g_free (notification->priv->app_name);
notification->priv->app_name = g_strdup (app_name);
g_object_notify (G_OBJECT (notification), "app-name");
}
/**
* notify_notification_set_hint_int32:
* @notification: The notification.
* @key: The hint.
* @value: The hint's value.
*
* Sets a hint with a 32-bit integer value.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_int32 (NotifyNotification *notification,
const char *key,
gint value)
{
notify_notification_set_hint (notification, key,
g_variant_new_int32 (value));
}
/**
* notify_notification_set_hint_uint32:
* @notification: The notification.
* @key: The hint.
* @value: The hint's value.
*
* Sets a hint with an unsigned 32-bit integer value.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_uint32 (NotifyNotification *notification,
const char *key,
guint value)
{
notify_notification_set_hint (notification, key,
g_variant_new_uint32 (value));
}
/**
* notify_notification_set_hint_double:
* @notification: The notification.
* @key: The hint.
* @value: The hint's value.
*
* Sets a hint with a double value.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_double (NotifyNotification *notification,
const char *key,
gdouble value)
{
notify_notification_set_hint (notification, key,
g_variant_new_double (value));
}
/**
* notify_notification_set_hint_byte:
* @notification: The notification.
* @key: The hint.
* @value: The hint's value.
*
* Sets a hint with a byte value.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_byte (NotifyNotification *notification,
const char *key,
guchar value)
{
notify_notification_set_hint (notification, key,
g_variant_new_byte (value));
}
/**
* notify_notification_set_hint_byte_array:
* @notification: The notification.
* @key: The hint.
* @value: (array length=len): The hint's value.
* @len: The length of the byte array.
*
* Sets a hint with a byte array value. The length of @value must be passed
* as @len.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_byte_array (NotifyNotification *notification,
const char *key,
const guchar *value,
gsize len)
{
gpointer value_dup;
g_return_if_fail (value != NULL || len == 0);
#ifdef GLIB_VERSION_2_68
value_dup = g_memdup2 (value, len);
#else
value_dup = g_memdup (value, len);
#endif
notify_notification_set_hint (notification, key,
g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
value_dup,
len,
TRUE,
g_free,
value_dup));
}
/**
* notify_notification_set_hint_string:
* @notification: The notification.
* @key: The hint.
* @value: The hint's value.
*
* Sets a hint with a string value.
*
* Deprecated: 0.6. Use notify_notification_set_hint() instead
*/
void
notify_notification_set_hint_string (NotifyNotification *notification,
const char *key,
const char *value)
{
if (value != NULL && value[0] != '\0') {
notify_notification_set_hint (notification,
key,
g_variant_new_string (value));
}
}
static gboolean
_remove_all (void)
{
return TRUE;
}
/**
* notify_notification_clear_hints:
* @notification: The notification.
*
* Clears all hints from the notification.
*/
void
notify_notification_clear_hints (NotifyNotification *notification)
{
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
g_hash_table_foreach_remove (notification->priv->hints,
(GHRFunc) _remove_all,
NULL);
}
/**
* notify_notification_clear_actions:
* @notification: The notification.
*
* Clears all actions from the notification.
*/
void
notify_notification_clear_actions (NotifyNotification *notification)
{
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
g_hash_table_foreach_remove (notification->priv->action_map,
(GHRFunc) _remove_all,
NULL);
if (notification->priv->actions != NULL) {
g_slist_foreach (notification->priv->actions,
(GFunc) g_free,
NULL);
g_slist_free (notification->priv->actions);
}
notification->priv->actions = NULL;
notification->priv->has_nondefault_actions = FALSE;
}
/**
* notify_notification_add_action:
* @notification: The notification.
* @action: The action ID.
* @label: The human-readable action label.
* @callback: The action's callback function.
* @user_data: Optional custom data to pass to @callback.
* @free_func: (type GLib.DestroyNotify): An optional function to free @user_data when the notification
* is destroyed.
*
* Adds an action to a notification. When the action is invoked, the
* specified callback function will be called, along with the value passed
* to @user_data.
*/
void
notify_notification_add_action (NotifyNotification *notification,
const char *action,
const char *label,
NotifyActionCallback callback,
gpointer user_data,
GFreeFunc free_func)
{
NotifyNotificationPrivate *priv;
CallbackPair *pair;
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
g_return_if_fail (action != NULL && *action != '\0');
g_return_if_fail (label != NULL && *label != '\0');
g_return_if_fail (callback != NULL);
priv = notification->priv;
priv->actions = g_slist_append (priv->actions, g_strdup (action));
priv->actions = g_slist_append (priv->actions, g_strdup (label));
pair = g_new0 (CallbackPair, 1);
pair->cb = callback;
pair->user_data = user_data;
pair->free_func = free_func;
g_hash_table_insert (priv->action_map, g_strdup (action), pair);
if (!notification->priv->has_nondefault_actions &&
g_ascii_strcasecmp (action, "default") != 0) {
notification->priv->has_nondefault_actions = TRUE;
}
}
/**
* notify_notification_get_activation_token:
*
* If an an action is currently being activated, return the activation token.
* This function is intended to be used in a #NotifyActionCallback to get
* the activation token for the activated action, if the notification daemon
* supports it.
*
* Return value: (transfer none): The current activation token, or %NULL if none
*
* Since: 0.7.10
*/
const char *
notify_notification_get_activation_token (NotifyNotification *notification)
{
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), NULL);
if (notification->priv->activating)
return notification->priv->activation_token;
return NULL;
}
gboolean
_notify_notification_has_nondefault_actions (const NotifyNotification *n)
{
g_return_val_if_fail (n != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (n), FALSE);
return n->priv->has_nondefault_actions;
}
/**
* notify_notification_close:
* @notification: The notification.
* @error: The returned error information.
*
* Synchronously tells the notification server to hide the notification on the screen.
*
* Returns: %TRUE on success, or %FALSE on error with @error filled in
*/
gboolean
notify_notification_close (NotifyNotification *notification,
GError **error)
{
NotifyNotificationPrivate *priv;
GDBusProxy *proxy;
GVariant *result;
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
priv = notification->priv;
proxy = _notify_get_proxy (error);
if (proxy == NULL) {
return FALSE;
}
/* FIXME: make this nonblocking! */
result = g_dbus_proxy_call_sync (proxy,
"CloseNotification",
g_variant_new ("(u)", priv->id),
G_DBUS_CALL_FLAGS_NONE,
-1 /* FIXME! */,
NULL,
error);
if (result == NULL) {
return FALSE;
}
g_variant_unref (result);
return TRUE;
}
/**
* notify_notification_get_closed_reason:
* @notification: The notification.
*
* Returns the closed reason code for the notification. This is valid only
* after the "closed" signal is emitted.
*
* Returns: The closed reason code.
*/
gint
notify_notification_get_closed_reason (const NotifyNotification *notification)
{
g_return_val_if_fail (notification != NULL, -1);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
return notification->priv->closed_reason;
}