/* -*- 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 #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 (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; }