glib/gio/gnotification.c
Christian Hergert 0f95b18a7b gio: use g_variant_builder_init_static()
All uses of g_variant_builder_init() in gio are safe to translate to the
new g_variant_builder_init_static() alternative as the type will outlive
the call to g_variant_builder_end() (or is already static in nature).
2024-09-26 12:48:16 +01:00

858 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2013 Lars Uebernickel
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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, see <http://www.gnu.org/licenses/>.
*
* Authors: Lars Uebernickel <lars@uebernic.de>
*/
#include "config.h"
#include "gnotification-private.h"
#include "gdbusutils.h"
#include "gicon.h"
#include "gaction.h"
#include "gioenumtypes.h"
/**
* GNotification:
*
* `GNotification` is a mechanism for creating a notification to be shown
* to the user — typically as a pop-up notification presented by the
* desktop environment shell.
*
* The key difference between `GNotification` and other similar APIs is
* that, if supported by the desktop environment, notifications sent
* with `GNotification` will persist after the application has exited,
* and even across system reboots.
*
* Since the user may click on a notification while the application is
* not running, applications using `GNotification` should be able to be
* started as a D-Bus service, using [class@Gio.Application].
*
* In order for `GNotification` to work, the application must have installed
* a `.desktop` file. For example:
* ```
* [Desktop Entry]
* Name=Test Application
* Comment=Description of what Test Application does
* Exec=gnome-test-application
* Icon=org.gnome.TestApplication
* Terminal=false
* Type=Application
* Categories=GNOME;GTK;TestApplication Category;
* StartupNotify=true
* DBusActivatable=true
* X-GNOME-UsesNotifications=true
* ```
*
* The `X-GNOME-UsesNotifications` key indicates to GNOME Control Center
* that this application uses notifications, so it can be listed in the
* Control Centers Notifications panel.
*
* The `.desktop` file must be named as `org.gnome.TestApplication.desktop`,
* where `org.gnome.TestApplication` is the ID passed to
* [ctor@Gio.Application.new].
*
* User interaction with a notification (either the default action, or
* buttons) must be associated with actions on the application (ie:
* `app.` actions). It is not possible to route user interaction
* through the notification itself, because the object will not exist if
* the application is autostarted as a result of a notification being
* clicked.
*
* A notification can be sent with [method@Gio.Application.send_notification].
*
* Since: 2.40
**/
typedef GObjectClass GNotificationClass;
struct _GNotification
{
GObject parent;
gchar *title;
gchar *body;
GIcon *icon;
GNotificationPriority priority;
gchar *category;
GPtrArray *buttons;
gchar *default_action;
GVariant *default_action_target; /* (nullable) (owned), not floating */
};
typedef struct
{
gchar *label;
gchar *action_name;
GVariant *target;
} Button;
G_DEFINE_TYPE (GNotification, g_notification, G_TYPE_OBJECT)
static void
button_free (gpointer data)
{
Button *button = data;
g_free (button->label);
g_free (button->action_name);
if (button->target)
g_variant_unref (button->target);
g_slice_free (Button, button);
}
static void
g_notification_dispose (GObject *object)
{
GNotification *notification = G_NOTIFICATION (object);
g_clear_object (&notification->icon);
G_OBJECT_CLASS (g_notification_parent_class)->dispose (object);
}
static void
g_notification_finalize (GObject *object)
{
GNotification *notification = G_NOTIFICATION (object);
g_free (notification->title);
g_free (notification->body);
g_free (notification->category);
g_free (notification->default_action);
if (notification->default_action_target)
g_variant_unref (notification->default_action_target);
g_ptr_array_free (notification->buttons, TRUE);
G_OBJECT_CLASS (g_notification_parent_class)->finalize (object);
}
static void
g_notification_class_init (GNotificationClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = g_notification_dispose;
object_class->finalize = g_notification_finalize;
}
static void
g_notification_init (GNotification *notification)
{
notification->buttons = g_ptr_array_new_full (2, button_free);
}
/**
* g_notification_new:
* @title: the title of the notification
*
* Creates a new #GNotification with @title as its title.
*
* After populating @notification with more details, it can be sent to
* the desktop shell with g_application_send_notification(). Changing
* any properties after this call will not have any effect until
* resending @notification.
*
* Returns: a new #GNotification instance
*
* Since: 2.40
*/
GNotification *
g_notification_new (const gchar *title)
{
GNotification *notification;
g_return_val_if_fail (title != NULL, NULL);
notification = g_object_new (G_TYPE_NOTIFICATION, NULL);
notification->title = g_strdup (title);
return notification;
}
/*< private >
* g_notification_get_title:
* @notification: a #GNotification
*
* Gets the title of @notification.
*
* Returns: the title of @notification
*
* Since: 2.40
*/
const gchar *
g_notification_get_title (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
return notification->title;
}
/**
* g_notification_set_title:
* @notification: a #GNotification
* @title: the new title for @notification
*
* Sets the title of @notification to @title.
*
* Since: 2.40
*/
void
g_notification_set_title (GNotification *notification,
const gchar *title)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (title != NULL);
g_free (notification->title);
notification->title = g_strdup (title);
}
/*< private >
* g_notification_get_body:
* @notification: a #GNotification
*
* Gets the current body of @notification.
*
* Returns: (nullable): the body of @notification
*
* Since: 2.40
*/
const gchar *
g_notification_get_body (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
return notification->body;
}
/**
* g_notification_set_body:
* @notification: a #GNotification
* @body: (nullable): the new body for @notification, or %NULL
*
* Sets the body of @notification to @body.
*
* Since: 2.40
*/
void
g_notification_set_body (GNotification *notification,
const gchar *body)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (body != NULL);
g_free (notification->body);
notification->body = g_strdup (body);
}
/*< private >
* g_notification_get_icon:
* @notification: a #GNotification
*
* Gets the icon currently set on @notification.
*
* Returns: (transfer none): the icon associated with @notification
*
* Since: 2.40
*/
GIcon *
g_notification_get_icon (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
return notification->icon;
}
/**
* g_notification_set_icon:
* @notification: a #GNotification
* @icon: the icon to be shown in @notification, as a #GIcon
*
* Sets the icon of @notification to @icon.
*
* Since: 2.40
*/
void
g_notification_set_icon (GNotification *notification,
GIcon *icon)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
if (notification->icon)
g_object_unref (notification->icon);
notification->icon = g_object_ref (icon);
}
/*< private >
* g_notification_get_priority:
* @notification: a #GNotification
*
* Returns the priority of @notification
*
* Since: 2.42
*/
GNotificationPriority
g_notification_get_priority (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), G_NOTIFICATION_PRIORITY_NORMAL);
return notification->priority;
}
/**
* g_notification_set_urgent:
* @notification: a #GNotification
* @urgent: %TRUE if @notification is urgent
*
* Deprecated in favor of g_notification_set_priority().
*
* Since: 2.40
* Deprecated: 2.42: Since 2.42, this has been deprecated in favour of
* g_notification_set_priority().
*/
void
g_notification_set_urgent (GNotification *notification,
gboolean urgent)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
notification->priority = urgent ?
G_NOTIFICATION_PRIORITY_URGENT :
G_NOTIFICATION_PRIORITY_NORMAL;
}
/*< private >
* g_notification_get_category:
* @notification: a #GNotification
*
* Gets the category of @notification.
*
* This will be %NULL if no category is set.
*
* Returns: (nullable): the category of @notification
*
* Since: 2.70
*/
const gchar *
g_notification_get_category (GNotification *notification)
{
g_return_val_if_fail (G_IS_NOTIFICATION (notification), NULL);
return notification->category;
}
/**
* g_notification_set_category:
* @notification: a #GNotification
* @category: (nullable): the category for @notification, or %NULL for no category
*
* Sets the type of @notification to @category. Categories have a main
* type like `email`, `im` or `device` and can have a detail separated
* by a `.`, e.g. `im.received` or `email.arrived`. Setting the category
* helps the notification server to select proper feedback to the user.
*
* Standard categories are [listed in the specification](https://specifications.freedesktop.org/notification-spec/latest/ar01s06.html).
*
* Since: 2.70
*/
void
g_notification_set_category (GNotification *notification,
const gchar *category)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (category == NULL || *category != '\0');
g_free (notification->category);
notification->category = g_strdup (category);
}
/**
* g_notification_set_priority:
* @notification: a #GNotification
* @priority: a #GNotificationPriority
*
* Sets the priority of @notification to @priority. See
* #GNotificationPriority for possible values.
*/
void
g_notification_set_priority (GNotification *notification,
GNotificationPriority priority)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
notification->priority = priority;
}
/**
* g_notification_add_button:
* @notification: a #GNotification
* @label: label of the button
* @detailed_action: a detailed action name
*
* Adds a button to @notification that activates the action in
* @detailed_action when clicked. That action must be an
* application-wide action (starting with "app."). If @detailed_action
* contains a target, the action will be activated with that target as
* its parameter.
*
* See g_action_parse_detailed_name() for a description of the format
* for @detailed_action.
*
* Since: 2.40
*/
void
g_notification_add_button (GNotification *notification,
const gchar *label,
const gchar *detailed_action)
{
gchar *action;
GVariant *target;
GError *error = NULL;
g_return_if_fail (detailed_action != NULL);
if (!g_action_parse_detailed_name (detailed_action, &action, &target, &error))
{
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
return;
}
g_notification_add_button_with_target_value (notification, label, action, target);
g_free (action);
if (target)
g_variant_unref (target);
}
/**
* g_notification_add_button_with_target: (skip)
* @notification: a #GNotification
* @label: label of the button
* @action: an action name
* @target_format: (nullable): a #GVariant format string, or %NULL
* @...: positional parameters, as determined by @target_format
*
* Adds a button to @notification that activates @action when clicked.
* @action must be an application-wide action (it must start with "app.").
*
* If @target_format is given, it is used to collect remaining
* positional parameters into a #GVariant instance, similar to
* g_variant_new(). @action will be activated with that #GVariant as its
* parameter.
*
* Since: 2.40
*/
void
g_notification_add_button_with_target (GNotification *notification,
const gchar *label,
const gchar *action,
const gchar *target_format,
...)
{
va_list args;
GVariant *target = NULL;
if (target_format)
{
va_start (args, target_format);
target = g_variant_new_va (target_format, NULL, &args);
va_end (args);
}
g_notification_add_button_with_target_value (notification, label, action, target);
}
/**
* g_notification_add_button_with_target_value: (rename-to g_notification_add_button_with_target)
* @notification: a #GNotification
* @label: label of the button
* @action: an action name
* @target: (nullable): a #GVariant to use as @action's parameter, or %NULL
*
* Adds a button to @notification that activates @action when clicked.
* @action must be an application-wide action (it must start with "app.").
*
* If @target is non-%NULL, @action will be activated with @target as
* its parameter.
*
* Since: 2.40
*/
void
g_notification_add_button_with_target_value (GNotification *notification,
const gchar *label,
const gchar *action,
GVariant *target)
{
Button *button;
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (label != NULL);
g_return_if_fail (action != NULL && g_action_name_is_valid (action));
if (!g_str_has_prefix (action, "app."))
{
g_warning ("%s: action '%s' does not start with 'app.'."
"This is unlikely to work properly.", G_STRFUNC, action);
}
button = g_slice_new0 (Button);
button->label = g_strdup (label);
button->action_name = g_strdup (action);
if (target)
button->target = g_variant_ref_sink (target);
g_ptr_array_add (notification->buttons, button);
}
/*< private >
* g_notification_get_n_buttons:
* @notification: a #GNotification
*
* Returns: the amount of buttons added to @notification.
*/
guint
g_notification_get_n_buttons (GNotification *notification)
{
return notification->buttons->len;
}
/*< private >
* g_notification_get_button:
* @notification: a #GNotification
* @index: index of the button
* @label: (): return location for the button's label
* @action: (): return location for the button's associated action
* @target: (): return location for the target @action should be
* activated with
*
* Returns a description of a button that was added to @notification
* with g_notification_add_button().
*
* @index must be smaller than the value returned by
* g_notification_get_n_buttons().
*/
void
g_notification_get_button (GNotification *notification,
gint index,
gchar **label,
gchar **action,
GVariant **target)
{
Button *button;
button = g_ptr_array_index (notification->buttons, index);
if (label)
*label = g_strdup (button->label);
if (action)
*action = g_strdup (button->action_name);
if (target)
*target = button->target ? g_variant_ref (button->target) : NULL;
}
/*< private >
* g_notification_get_button_with_action:
* @notification: a #GNotification
* @action: an action name
*
* Returns the index of the button in @notification that is associated
* with @action, or -1 if no such button exists.
*/
gint
g_notification_get_button_with_action (GNotification *notification,
const gchar *action)
{
guint i;
for (i = 0; i < notification->buttons->len; i++)
{
Button *button;
button = g_ptr_array_index (notification->buttons, i);
if (g_str_equal (action, button->action_name))
return i;
}
return -1;
}
/*< private >
* g_notification_get_default_action:
* @notification: a #GNotification
* @action: (out) (optional) (nullable) (transfer full): return location for the
* default action, or %NULL if unset
* @target: (out) (optional) (nullable) (transfer full): return location for the
* target of the default action, or %NULL if unset
*
* Gets the action and target for the default action of @notification.
*
* If this function returns %TRUE, @action is guaranteed to be set to a non-%NULL
* value (if a pointer is passed to @action). @target may still return a %NULL
* value, as the default action may have no target.
*
* Returns: %TRUE if @notification has a default action
*/
gboolean
g_notification_get_default_action (GNotification *notification,
gchar **action,
GVariant **target)
{
if (notification->default_action == NULL)
return FALSE;
if (action)
*action = g_strdup (notification->default_action);
if (target)
{
if (notification->default_action_target)
*target = g_variant_ref (notification->default_action_target);
else
*target = NULL;
}
return TRUE;
}
/**
* g_notification_set_default_action:
* @notification: a #GNotification
* @detailed_action: a detailed action name
*
* Sets the default action of @notification to @detailed_action. This
* action is activated when the notification is clicked on.
*
* The action in @detailed_action must be an application-wide action (it
* must start with "app."). If @detailed_action contains a target, the
* given action will be activated with that target as its parameter.
* See g_action_parse_detailed_name() for a description of the format
* for @detailed_action.
*
* When no default action is set, the application that the notification
* was sent on is activated.
*
* Since: 2.40
*/
void
g_notification_set_default_action (GNotification *notification,
const gchar *detailed_action)
{
gchar *action;
GVariant *target;
GError *error = NULL;
if (!g_action_parse_detailed_name (detailed_action, &action, &target, &error))
{
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
return;
}
g_notification_set_default_action_and_target_value (notification, action, target);
g_free (action);
if (target)
g_variant_unref (target);
}
/**
* g_notification_set_default_action_and_target: (skip)
* @notification: a #GNotification
* @action: an action name
* @target_format: (nullable): a #GVariant format string, or %NULL
* @...: positional parameters, as determined by @target_format
*
* Sets the default action of @notification to @action. This action is
* activated when the notification is clicked on. It must be an
* application-wide action (it must start with "app.").
*
* If @target_format is given, it is used to collect remaining
* positional parameters into a #GVariant instance, similar to
* g_variant_new(). @action will be activated with that #GVariant as its
* parameter.
*
* When no default action is set, the application that the notification
* was sent on is activated.
*
* Since: 2.40
*/
void
g_notification_set_default_action_and_target (GNotification *notification,
const gchar *action,
const gchar *target_format,
...)
{
va_list args;
GVariant *target = NULL;
if (target_format)
{
va_start (args, target_format);
target = g_variant_new_va (target_format, NULL, &args);
va_end (args);
}
g_notification_set_default_action_and_target_value (notification, action, target);
}
/**
* g_notification_set_default_action_and_target_value: (rename-to g_notification_set_default_action_and_target)
* @notification: a #GNotification
* @action: an action name
* @target: (nullable): a #GVariant to use as @action's parameter, or %NULL
*
* Sets the default action of @notification to @action. This action is
* activated when the notification is clicked on. It must be an
* application-wide action (start with "app.").
*
* If @target is non-%NULL, @action will be activated with @target as
* its parameter. If @target is floating, it will be consumed.
*
* When no default action is set, the application that the notification
* was sent on is activated.
*
* Since: 2.40
*/
void
g_notification_set_default_action_and_target_value (GNotification *notification,
const gchar *action,
GVariant *target)
{
g_return_if_fail (G_IS_NOTIFICATION (notification));
g_return_if_fail (action != NULL && g_action_name_is_valid (action));
if (!g_str_has_prefix (action, "app."))
{
g_warning ("%s: action '%s' does not start with 'app.'."
"This is unlikely to work properly.", G_STRFUNC, action);
}
g_free (notification->default_action);
g_clear_pointer (&notification->default_action_target, g_variant_unref);
notification->default_action = g_strdup (action);
if (target)
notification->default_action_target = g_variant_ref_sink (target);
}
static GVariant *
g_notification_serialize_button (Button *button)
{
GVariantBuilder builder;
g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", "label", g_variant_new_string (button->label));
g_variant_builder_add (&builder, "{sv}", "action", g_variant_new_string (button->action_name));
if (button->target)
g_variant_builder_add (&builder, "{sv}", "target", button->target);
return g_variant_builder_end (&builder);
}
static GVariant *
g_notification_get_priority_nick (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;
}
/*< private >
* g_notification_serialize:
*
* Serializes @notification into a floating variant of type a{sv}.
*
* Returns: the serialized @notification as a floating variant.
*/
GVariant *
g_notification_serialize (GNotification *notification)
{
GVariantBuilder builder;
g_variant_builder_init_static (&builder, G_VARIANT_TYPE ("a{sv}"));
if (notification->title)
g_variant_builder_add (&builder, "{sv}", "title", g_variant_new_string (notification->title));
if (notification->body)
g_variant_builder_add (&builder, "{sv}", "body", g_variant_new_string (notification->body));
if (notification->icon)
{
GVariant *serialized_icon;
if ((serialized_icon = g_icon_serialize (notification->icon)))
{
g_variant_builder_add (&builder, "{sv}", "icon", serialized_icon);
g_variant_unref (serialized_icon);
}
}
g_variant_builder_add (&builder, "{sv}", "priority", g_notification_get_priority_nick (notification));
if (notification->default_action)
{
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string (notification->default_action));
if (notification->default_action_target)
g_variant_builder_add (&builder, "{sv}", "default-action-target",
notification->default_action_target);
}
if (notification->buttons->len > 0)
{
GVariantBuilder actions_builder;
guint i;
g_variant_builder_init_static (&actions_builder, G_VARIANT_TYPE ("aa{sv}"));
for (i = 0; i < notification->buttons->len; i++)
{
Button *button = g_ptr_array_index (notification->buttons, i);
g_variant_builder_add (&actions_builder, "@a{sv}", g_notification_serialize_button (button));
}
g_variant_builder_add (&builder, "{sv}", "buttons", g_variant_builder_end (&actions_builder));
}
return g_variant_builder_end (&builder);
}