Compare commits

..

1 Commits

Author SHA1 Message Date
Logan Rathbone 72d66bd305 Revert "Merge branch 'readme' into 'master'"
This reverts merge request !21
2022-03-21 22:15:30 +00:00
18 changed files with 100 additions and 1404 deletions

View File

@ -26,12 +26,4 @@ build:ubuntu:
- pip3 install meson - pip3 install meson
script: script:
- meson _build -Ddocbook_docs=enabled - meson _build -Ddocbook_docs=enabled
- ninja -C _build install - ninja -C _build
artifacts:
expose_as: "Build artifacts"
paths:
- _build/docs/notification-spec.html
- _build/docs/reference/html
- _build/docs/reference/html/index.html
- _build/meson-logs

68
NEWS
View File

@ -1,71 +1,3 @@
New in 0.8.1
============
* Keep version of bindings at 0.7 (#27) [Marco]
Contributors:
Marco Trevisan
New in 0.8.0
============
* Use Desktop Portal Notification when running confined (snap and flatpak)
Now the library acts like a wrapper in such scenario, with some limited
capabilities, but this will enforce security and user control over the
allowed notifications. [Marco]
* notify-send: Handles SIGINT gracefully, closing waiting notification [Marco]
* Use NotifyClosedReason enum as closed reason return value [Marco]
* Bump dependency on GLib 2.38 [Marco]
* Various introspection docs improvements and fixes [Marco]
Contributors:
Marco Trevisan
New in 0.7.12
=============
* docs/notify-send: Add --transient option to manpage [Marco]
* notify-send: Move server capabilities check to a separate function [Marco]
* notify-send: Add debug message about server not supporting persistence
[Marco]
* notification: Include sender-pid hint by default if not provided [Marco]
* Delete unused notifynotification.xml [Patrick; !25]
* notification: Bookend calling NotifyActionCallback with temporary ref
[Logan; #25, !26]
Contributors:
Marco Trevisan, Patrick Griffis, Logan Rathbone
New in 0.7.11
=============
* Fix potential build errors with old glib version we require
* notify-send: Add support for boolean hints
* notify-send: Support passing any hint value, by parsing variant strings
* notify-send: Add explicit option to create transient notifications
Contributors:
Marco Trevisan
New in 0.7.10
=============
* notify-send: Support commas in icon filenames [Thorsten; !15]
* notify-send: Give failing exit code if showing notification fails [Ray, !13]
* notify-send: Support for replacing an existing notification [Paul; !17]
* notify-send: Add option to wait until notification has been closed [Ben; !18]
* notify-send: Add support for notification actions and responses [Ben; !18]
* notification: Send the application ID when possible [Corentin; !1]
* notification: Use g_memdup2 when available [Marco; !22]
* notification: Improve SNAP detection and confined desktop ID [Marco; !23]
* notification: Add support for getting actions activation token [Marco; !24]
* notify: Use application ID if any to set the fallback app name [Marco; !18]
* Build fixes and improvements [Marco; !22]
* Docs updates [Boris, David; !14, !20]
Contributors:
Marco Trevisan, Boris Shtrasman, Matthias Sweertvaegher, Thorsten Wißmann,
Ray Strode, Maximiliano Sandoval R, David King, Corentin Noël, Paul Collins,
Matthias Sweertvaegher, Ben Blain
New in 0.7.9 New in 0.7.9
============ ============
* Fixed linking in darwin [Iain, Marco; !5] * Fixed linking in darwin [Iain, Marco; !5]

View File

@ -1,20 +0,0 @@
# libnotify
## Description
libnotify is a library for sending desktop notifications to a notification
daemon, as defined in the [org.freedesktop.Notifications][fdo-spec] Desktop
Specification. These notifications can be used to inform the user about an event
or display some form of information without getting in the user's way.
It is also a simple wrapper to send cross-desktop Notifications for sandboxed
applications using the [XDG Portal Notification API][portal].
## Notice
For GLib based applications the [GNotification][gnotif] API should be used
instead.
[fdo-spec]: https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html
[gnotif]: https://docs.gtk.org/gio/class.Notification.html
[portal]: https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.Notification.xml

View File

@ -3,5 +3,4 @@
xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="1.0"> version="1.0">
<xsl:param name="html.stylesheet" select="'docbook.css'"/> <xsl:param name="html.stylesheet" select="'docbook.css'"/>
<xsl:param name="generate.consistent.ids" select="1"/>
</xsl:stylesheet> </xsl:stylesheet>

View File

@ -1272,65 +1272,6 @@
</para> </para>
</note> </note>
</sect3> </sect3>
<sect3 id="signal-activation-token">
<title><literal>org.freedesktop.Notifications.ActivationToken</literal></title>
<funcsynopsis>
<funcprototype>
<funcdef>
<function>org.freedesktop.Notifications.ActivationToken</function>
</funcdef>
<paramdef>UINT32 <parameter>id</parameter></paramdef>
<paramdef>STRING <parameter>activation_token</parameter></paramdef>
</funcprototype>
</funcsynopsis>
<para>
This signal can be emitted before a <literal>ActionInvoked</literal>
signal. It carries an activation token that can be used to activate a
toplevel.
</para>
<table>
<title>ActivationToken Parameters</title>
<tgroup cols="2">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody valign="top">
<row>
<entry><parameter>id</parameter></entry>
<entry>UINT32</entry>
<entry>
The ID of the notification emitting the <literal>ActionInvoked</literal>
signal.
</entry>
</row>
<row>
<entry><parameter>activation_token</parameter></entry>
<entry>STRING</entry>
<entry>
An activation token. This can be either an X11-style startup ID (see
<ulink url="https://specifications.freedesktop.org/startup-notification-spec/startup-notification-latest.txt">Startup notification protocol</ulink>)
or a
<ulink url="https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/staging/xdg-activation">Wayland xdg-activation</ulink>
token.
</entry>
</row>
</tbody>
</tgroup>
</table>
<note>
<para>
Clients should not assume the server will generate this signal. Some
servers may not support user interaction at all, or may not support
the concept of being able to generate an activation token for a
notification.
</para>
</note>
</sect3>
</sect2> </sect2>
</sect1> </sect1>
</article> </article>

View File

@ -58,18 +58,6 @@
<para>Show help and exit.</para> <para>Show help and exit.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>-a</option>, <option>--app-name</option>=<replaceable>APP_NAME</replaceable></term>
<listitem>
<para>Specifies the app name for the notification.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-A</option>, <option>--action</option>=[<replaceable>NAME</replaceable>=]<replaceable>Text...</replaceable></term>
<listitem>
<para>Specifies the actions to display to the user. Implies <option>--wait</option> to wait for user input. May be set multiple times. The <replaceable>NAME</replaceable> of the action is output to <literal>stdout</literal>. If <replaceable>NAME</replaceable> is not specified, the numerical index of the option is used (starting with <literal>1</literal>).</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>-u</option>, <option>--urgency</option>=<replaceable>LEVEL</replaceable></term> <term><option>-u</option>, <option>--urgency</option>=<replaceable>LEVEL</replaceable></term>
<listitem> <listitem>
@ -101,31 +89,7 @@
<varlistentry> <varlistentry>
<term><option>-h</option>, <option>--hint</option>=<replaceable>TYPE</replaceable>:<replaceable>NAME</replaceable>:<replaceable>VALUE</replaceable> </term> <term><option>-h</option>, <option>--hint</option>=<replaceable>TYPE</replaceable>:<replaceable>NAME</replaceable>:<replaceable>VALUE</replaceable> </term>
<listitem> <listitem>
<para>Specifies basic extra data to pass. Valid types are <literal>BOOLEAN</literal>, <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal>, <literal>BYTE</literal> and <literal>VARIANT</literal>.</para> <para>Specifies basic extra data to pass. Valid types are <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal> and <literal>BYTE</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-p</option>, <option>--print-id</option></term>
<listitem>
<para>Print the notification ID.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-r</option>, <option>--replace-id</option>=<replaceable>REPLACE_ID</replaceable></term>
<listitem>
<para>The ID of the notification to replace.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-w</option>, <option>--wait</option></term>
<listitem>
<para>Wait for the notification to be closed before exiting. If the <option>expire-time</option> is set, it will be used as the maximum waiting time.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-e</option>, <option>--transient</option></term>
<listitem>
<para>Show a transient notification. Transient notifications by-pass the server's persistence capability, if any. And so it won't be preserved until the user acknowledges it.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
@ -133,6 +97,6 @@
<refsection> <refsection>
<info><title>See also</title></info> <info><title>See also</title></info>
<para>The Desktop Notification Spec on <link xlink:href="https://specifications.freedesktop.org/notification-spec/">https://specifications.freedesktop.org/notification-spec/</link>.</para> <para>The Desktop Notification Spec on <link xlink:href="http://www.galago-project.org/specs/notification/">http://www.galago-project.org/specs/notification/</link>.</para>
</refsection> </refsection>
</refentry> </refentry>

View File

@ -4,7 +4,6 @@ NOTIFY_EXPIRES_DEFAULT
NOTIFY_EXPIRES_NEVER NOTIFY_EXPIRES_NEVER
<TITLE>NotifyNotification</TITLE> <TITLE>NotifyNotification</TITLE>
NotifyNotification NotifyNotification
NotifyClosedReason
NotifyUrgency NotifyUrgency
NotifyActionCallback NotifyActionCallback
NOTIFY_ACTION_CALLBACK NOTIFY_ACTION_CALLBACK
@ -28,7 +27,6 @@ notify_notification_clear_hints
notify_notification_add_action notify_notification_add_action
notify_notification_clear_actions notify_notification_clear_actions
notify_notification_close notify_notification_close
notify_notification_get_activation_token
notify_notification_get_closed_reason notify_notification_get_closed_reason
<SUBSECTION Standard> <SUBSECTION Standard>
NotifyNotificationPrivate NotifyNotificationPrivate

View File

@ -8,21 +8,18 @@
<category rdf:resource="http://api.gnome.org/doap-extensions#core" /> <category rdf:resource="http://api.gnome.org/doap-extensions#core" />
<programming-language>C</programming-language> <programming-language>C</programming-language>
<shortdesc xml:lang="en">libnotify is a library for sending desktop notifications</shortdesc> <shortdesc xml:lang="en">libnotify is a library for sending desktop notifications</shortdesc>
<description>Library to send desktop notifications that follows the FDO
specifications:
https://gitlab.freedesktop.org/xdg/xdg-specs/-/blob/master/notification/notification-spec.xml</description>
<maintainer> <maintainer>
<foaf:Person> <foaf:Person>
<foaf:name>Marco Trevisan</foaf:name> <foaf:name>Christian Hammond</foaf:name>
<foaf:mbox rdf:resource="mailto:mail@3v1n0.net" /> <foaf:mbox rdf:resource="mailto:chipx86@chipx86.com" />
<gnome:userid>marcotrevi</gnome:userid> <gnome:userid>chammond</gnome:userid>
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
<maintainer> <maintainer>
<foaf:Person> <foaf:Person>
<foaf:name>Logan Rathbone</foaf:name> <foaf:name>William Jon McCann</foaf:name>
<foaf:mbox rdf:resource="mailto:poprocks@gmail.com" /> <foaf:mbox rdf:resource="mailto:william.jon.mccann@gmail.com" />
<gnome:userid>larathbone</gnome:userid> <gnome:userid>mccann</gnome:userid>
</foaf:Person> </foaf:Person>
</maintainer> </maintainer>
</Project> </Project>

View File

@ -26,10 +26,6 @@
#define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications" #define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
#define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications" #define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
#define NOTIFY_PORTAL_DBUS_NAME "org.freedesktop.portal.Desktop"
#define NOTIFY_PORTAL_DBUS_CORE_INTERFACE "org.freedesktop.portal.Notification"
#define NOTIFY_PORTAL_DBUS_CORE_OBJECT "/org/freedesktop/portal/desktop"
G_BEGIN_DECLS G_BEGIN_DECLS
GDBusProxy * _notify_get_proxy (GError **error); GDBusProxy * _notify_get_proxy (GError **error);
@ -40,14 +36,6 @@ gint _notify_notification_get_timeout (const NotifyNotific
gboolean _notify_notification_has_nondefault_actions (const NotifyNotification *n); gboolean _notify_notification_has_nondefault_actions (const NotifyNotification *n);
gboolean _notify_check_spec_version (int major, int minor); gboolean _notify_check_spec_version (int major, int minor);
const char * _notify_get_snap_name (void);
const char * _notify_get_snap_path (void);
const char * _notify_get_snap_app (void);
const char * _notify_get_flatpak_app (void);
gboolean _notify_uses_portal_notifications (void);
G_END_DECLS G_END_DECLS
#endif /* _LIBNOTIFY_INTERNAL_H_ */ #endif /* _LIBNOTIFY_INTERNAL_H_ */

View File

@ -69,9 +69,8 @@ pkgconfig.generate(libnotify_lib,
) )
introspection = get_option('introspection') introspection = get_option('introspection')
g_ir_scanner = find_program('g-ir-scanner', required: introspection.enabled()) if not introspection.disabled()
find_program('g-ir-scanner', required: introspection.enabled())
if g_ir_scanner.found() and not introspection.disabled()
gnome.generate_gir(libnotify_lib, gnome.generate_gir(libnotify_lib,
sources: headers + sources + enum_types, sources: headers + sources + enum_types,
namespace: 'Notify', namespace: 'Notify',

View File

@ -53,7 +53,6 @@
static void notify_notification_class_init (NotifyNotificationClass *klass); static void notify_notification_class_init (NotifyNotificationClass *klass);
static void notify_notification_init (NotifyNotification *sp); static void notify_notification_init (NotifyNotification *sp);
static void notify_notification_finalize (GObject *object); static void notify_notification_finalize (GObject *object);
static void notify_notification_dispose (GObject *object);
typedef struct typedef struct
{ {
@ -69,11 +68,9 @@ struct _NotifyNotificationPrivate
char *app_name; char *app_name;
char *summary; char *summary;
char *body; char *body;
char *activation_token;
/* NULL to use icon data. Anything else to have server lookup icon */ /* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name; char *icon_name;
GdkPixbuf *icon_pixbuf;
/* /*
* -1 = use server default * -1 = use server default
@ -81,14 +78,12 @@ struct _NotifyNotificationPrivate
* > 0 = Number of milliseconds before we timeout * > 0 = Number of milliseconds before we timeout
*/ */
gint timeout; gint timeout;
guint portal_timeout_id;
GSList *actions; GSList *actions;
GHashTable *action_map; GHashTable *action_map;
GHashTable *hints; GHashTable *hints;
gboolean has_nondefault_actions; gboolean has_nondefault_actions;
gboolean activating;
gboolean updates_pending; gboolean updates_pending;
gulong proxy_signal_handler; gulong proxy_signal_handler;
@ -153,7 +148,6 @@ notify_notification_class_init (NotifyNotificationClass *klass)
object_class->constructor = notify_notification_constructor; object_class->constructor = notify_notification_constructor;
object_class->get_property = notify_notification_get_property; object_class->get_property = notify_notification_get_property;
object_class->set_property = notify_notification_set_property; object_class->set_property = notify_notification_set_property;
object_class->dispose = notify_notification_dispose;
object_class->finalize = notify_notification_finalize; object_class->finalize = notify_notification_finalize;
/** /**
@ -238,9 +232,9 @@ notify_notification_class_init (NotifyNotificationClass *klass)
g_param_spec_int ("closed-reason", g_param_spec_int ("closed-reason",
"Closed Reason", "Closed Reason",
"The reason code for why the notification was closed", "The reason code for why the notification was closed",
NOTIFY_CLOSED_REASON_UNSET, -1,
G_MAXINT32, G_MAXINT32,
NOTIFY_CLOSED_REASON_UNSET, -1,
G_PARAM_READABLE G_PARAM_READABLE
| G_PARAM_STATIC_NAME | G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK | G_PARAM_STATIC_NICK
@ -361,7 +355,7 @@ notify_notification_init (NotifyNotification *obj)
{ {
obj->priv = g_new0 (NotifyNotificationPrivate, 1); obj->priv = g_new0 (NotifyNotificationPrivate, 1);
obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT; obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
obj->priv->closed_reason = NOTIFY_CLOSED_REASON_UNSET; obj->priv->closed_reason = -1;
obj->priv->hints = g_hash_table_new_full (g_str_hash, obj->priv->hints = g_hash_table_new_full (g_str_hash,
g_str_equal, g_str_equal,
g_free, g_free,
@ -373,22 +367,6 @@ notify_notification_init (NotifyNotification *obj)
(GDestroyNotify) destroy_pair); (GDestroyNotify) destroy_pair);
} }
static void
notify_notification_dispose (GObject *object)
{
NotifyNotification *obj = NOTIFY_NOTIFICATION (object);
NotifyNotificationPrivate *priv = obj->priv;
if (priv->portal_timeout_id) {
g_source_remove (priv->portal_timeout_id);
priv->portal_timeout_id = 0;
}
g_clear_object (&priv->icon_pixbuf);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void static void
notify_notification_finalize (GObject *object) notify_notification_finalize (GObject *object)
{ {
@ -402,7 +380,6 @@ notify_notification_finalize (GObject *object)
g_free (priv->summary); g_free (priv->summary);
g_free (priv->body); g_free (priv->body);
g_free (priv->icon_name); g_free (priv->icon_name);
g_free (priv->activation_token);
if (priv->actions != NULL) { if (priv->actions != NULL) {
g_slist_foreach (priv->actions, (GFunc) g_free, NULL); g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
@ -425,18 +402,6 @@ notify_notification_finalize (GObject *object)
G_OBJECT_CLASS (parent_class)->finalize (object); G_OBJECT_CLASS (parent_class)->finalize (object);
} }
static gboolean
maybe_warn_portal_unsupported_feature (const char *feature_name)
{
if (!_notify_uses_portal_notifications ()) {
return FALSE;
}
g_message ("%s is not available when using Portal Notifications",
feature_name);
return TRUE;
}
/** /**
* notify_notification_new: * notify_notification_new:
* @summary: The required summary text. * @summary: The required summary text.
@ -466,52 +431,30 @@ try_prepend_path (const char *base_path,
{ {
gchar *path_filename; gchar *path_filename;
gchar *path_ret; gchar *path_ret;
gboolean was_uri;
if (!path || *path == '\0') if (!path || *path == '\0')
return NULL; return NULL;
was_uri = TRUE;
path_ret = NULL; path_ret = NULL;
path_filename = g_filename_from_uri (base_path, NULL, NULL); path_filename = g_filename_from_uri (base_path, NULL, NULL);
if (path_filename == NULL) { if (path_filename == NULL) {
was_uri = FALSE;
if (base_path && base_path[0] == G_DIR_SEPARATOR) { if (base_path && base_path[0] == G_DIR_SEPARATOR) {
path_filename = g_strdup (base_path); path_filename = g_strdup (base_path);
} else { } else {
path_filename = realpath (base_path, NULL); 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.", g_debug ("Trying to look at file '%s' in the '%s' prefix.",
base_path, base_path,
path); path);
path_ret = g_build_filename (path, path_filename, NULL); path_ret = g_build_filename (path, path_filename, NULL);
}
if (!g_file_test (path_ret, G_FILE_TEST_EXISTS)) { if (!g_file_test (path_ret, G_FILE_TEST_EXISTS)) {
g_debug ("Nothing found at %s", path_ret);
g_free (path_ret); g_free (path_ret);
path_ret = NULL; 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); g_free (path_filename);
@ -520,32 +463,33 @@ try_prepend_path (const char *base_path,
} }
static gchar * static gchar *
try_prepend_snap_desktop (NotifyNotification *notification, try_prepend_desktop (const gchar *desktop)
const gchar *desktop)
{ {
gchar *ret = NULL; gchar *ret;
/* /*
* if it's an absolute path, try prepending $SNAP, otherwise try * if it's an absolute path, try prepending $SNAP, otherwise try
* ${SNAP_NAME}_; snap .desktop files are in the format * $SNAP_NAME_; snap .desktop files are in the format
* ${SNAP_NAME}_desktop_file_name * ${SNAP_NAME}_desktop_file_name
*/ */
ret = try_prepend_path (desktop, _notify_get_snap_path ()); ret = try_prepend_path (desktop, g_getenv ("SNAP"));
if (ret == NULL && _notify_get_snap_name () != NULL && if (ret == NULL) {
strchr (desktop, G_DIR_SEPARATOR) == NULL) { const gchar *snap_name = g_getenv ("SNAP_NAME");
ret = g_strdup_printf ("%s_%s", _notify_get_snap_name (), desktop);
if (snap_name != NULL && snap_name[0] != '\0') {
ret = g_strdup_printf ("%s_%s", snap_name, desktop);
}
} }
return ret; return ret;
} }
static gchar * static gchar *
try_prepend_snap (NotifyNotification *notification, try_prepend_snap (const gchar *value)
const gchar *value)
{ {
/* hardcoded paths to icons might be relocated under $SNAP */ /* hardcoded paths to icons might be relocated under $SNAP */
return try_prepend_path (value, _notify_get_snap_path ()); return try_prepend_path (value, g_getenv ("SNAP"));
} }
@ -580,8 +524,7 @@ notify_notification_update_internal (NotifyNotification *notification,
g_free (notification->priv->icon_name); g_free (notification->priv->icon_name);
notification->priv->icon_name = (icon != NULL notification->priv->icon_name = (icon != NULL
&& *icon != '\0' ? g_strdup (icon) : NULL); && *icon != '\0' ? g_strdup (icon) : NULL);
snapped_icon = try_prepend_snap_desktop (notification, snapped_icon = try_prepend_desktop (notification->priv->icon_name);
notification->priv->icon_name);
if (snapped_icon != NULL) { if (snapped_icon != NULL) {
g_debug ("Icon updated in snap environment: '%s' -> '%s'\n", g_debug ("Icon updated in snap environment: '%s' -> '%s'\n",
notification->priv->icon_name, snapped_icon); notification->priv->icon_name, snapped_icon);
@ -624,80 +567,6 @@ notify_notification_update (NotifyNotification *notification,
return TRUE; return TRUE;
} }
static char *
get_portal_notification_id (NotifyNotification *notification)
{
char *app_id;
char *notification_id;
g_assert (_notify_uses_portal_notifications ());
if (_notify_get_snap_name ()) {
app_id = g_strdup_printf ("snap.%s_%s",
_notify_get_snap_name (),
_notify_get_snap_app ());
} else {
app_id = g_strdup_printf ("flatpak.%s",
_notify_get_flatpak_app ());
}
notification_id = g_strdup_printf ("libnotify-%s-%s-%u",
app_id,
notify_get_app_name (),
notification->priv->id);
g_free (app_id);
return notification_id;
}
static gboolean
activate_action (NotifyNotification *notification,
const gchar *action)
{
CallbackPair *pair;
pair = g_hash_table_lookup (notification->priv->action_map, action);
if (!pair) {
return FALSE;
}
/* Some clients have assumed it is safe to unref the
* Notification at the end of their NotifyActionCallback
* so we add a temporary ref until we're done with it.
*/
g_object_ref (notification);
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;
g_object_unref (notification);
return TRUE;
}
static gboolean
close_notification (NotifyNotification *notification,
NotifyClosedReason reason)
{
if (notification->priv->closed_reason != NOTIFY_CLOSED_REASON_UNSET ||
reason == NOTIFY_CLOSED_REASON_UNSET) {
return FALSE;
}
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));
return TRUE;
}
static void static void
proxy_g_signal_cb (GDBusProxy *proxy, proxy_g_signal_cb (GDBusProxy *proxy,
const char *sender_name, const char *sender_name,
@ -705,12 +574,8 @@ proxy_g_signal_cb (GDBusProxy *proxy,
GVariant *parameters, GVariant *parameters,
NotifyNotification *notification) NotifyNotification *notification)
{ {
const char *interface;
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
interface = g_dbus_proxy_get_interface_name (proxy);
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 && if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) { g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
guint32 id, reason; guint32 id, reason;
@ -719,332 +584,33 @@ proxy_g_signal_cb (GDBusProxy *proxy,
if (id != notification->priv->id) if (id != notification->priv->id)
return; return;
close_notification (notification, reason); 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 && } else if (g_strcmp0 (signal_name, "ActionInvoked") == 0 &&
g_str_equal (interface, NOTIFY_DBUS_CORE_INTERFACE) &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) { g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(us)"))) {
guint32 id; guint32 id;
const char *action; const char *action;
CallbackPair *pair;
g_variant_get (parameters, "(u&s)", &id, &action); g_variant_get (parameters, "(u&s)", &id, &action);
if (id != notification->priv->id) if (id != notification->priv->id)
return; return;
if (!activate_action (notification, action) && pair = (CallbackPair *) g_hash_table_lookup (notification->priv->action_map,
g_ascii_strcasecmp (action, "default")) { action);
if (pair == NULL) {
if (g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action); g_warning ("Received unknown action %s", action);
} }
} 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);
} else if (g_str_equal (signal_name, "ActionInvoked") &&
g_str_equal (interface, NOTIFY_PORTAL_DBUS_CORE_INTERFACE) &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssav)"))) {
char *notification_id;
const char *id;
const char *action;
GVariant *parameter;
g_variant_get (parameters, "(&s&s@av)", &id, &action, &parameter);
g_variant_unref (parameter);
notification_id = get_portal_notification_id (notification);
if (!g_str_equal (notification_id, id)) {
g_free (notification_id);
return;
}
if (!activate_action (notification, action) &&
g_str_equal (action, "default-action") &&
!_notify_get_snap_app ()) {
g_warning ("Received unknown action %s", action);
}
close_notification (notification, NOTIFY_CLOSED_REASON_DISMISSED);
g_free (notification_id);
} else { } else {
g_debug ("Unhandled signal '%s.%s'", interface, signal_name); pair->cb (notification, (char *) action, pair->user_data);
}
}
static gboolean
remove_portal_notification (GDBusProxy *proxy,
NotifyNotification *notification,
NotifyClosedReason reason,
GError **error)
{
GVariant *ret;
gchar *notification_id;
if (notification->priv->portal_timeout_id) {
g_source_remove (notification->priv->portal_timeout_id);
notification->priv->portal_timeout_id = 0;
}
notification_id = get_portal_notification_id (notification);
ret = g_dbus_proxy_call_sync (proxy,
"RemoveNotification",
g_variant_new ("(s)", notification_id),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
g_free (notification_id);
if (!ret) {
return FALSE;
}
close_notification (notification, reason);
g_variant_unref (ret);
return TRUE;
}
static gboolean
on_portal_timeout (gpointer data)
{
NotifyNotification *notification = data;
GDBusProxy *proxy;
notification->priv->portal_timeout_id = 0;
proxy = _notify_get_proxy (NULL);
if (proxy == NULL) {
return FALSE;
}
remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_EXPIRED, NULL);
return FALSE;
}
static GIcon *
get_notification_gicon (NotifyNotification *notification,
GError **error)
{
NotifyNotificationPrivate *priv = notification->priv;
GFileInputStream *input;
GFile *file = NULL;
GIcon *gicon = NULL;
if (priv->icon_pixbuf) {
return G_ICON (g_object_ref (priv->icon_pixbuf));
}
if (!priv->icon_name) {
return NULL;
}
if (strstr (priv->icon_name, "://")) {
file = g_file_new_for_uri (priv->icon_name);
} else if (g_file_test (priv->icon_name, G_FILE_TEST_EXISTS)) {
file = g_file_new_for_path (priv->icon_name);
} else {
gicon = g_themed_icon_new (priv->icon_name);
}
if (!file) {
return gicon;
}
input = g_file_read (file, NULL, error);
if (input) {
GByteArray *bytes_array = g_byte_array_new ();
guint8 buf[1024];
while (TRUE) {
gssize read;
read = g_input_stream_read (G_INPUT_STREAM (input),
buf,
G_N_ELEMENTS (buf),
NULL, NULL);
if (read > 0) {
g_byte_array_append (bytes_array, buf, read);
} else {
if (read < 0) {
g_byte_array_unref (bytes_array);
bytes_array = NULL;
}
break;
} }
} }
if (bytes_array && bytes_array->len) {
GBytes *bytes;
bytes = g_byte_array_free_to_bytes (bytes_array);
bytes_array = NULL;
gicon = g_bytes_icon_new (bytes);
} else if (bytes_array) {
g_byte_array_unref (bytes_array);
}
}
g_clear_object (&input);
g_clear_object (&file);
return gicon;
}
static gboolean
add_portal_notification (GDBusProxy *proxy,
NotifyNotification *notification,
GError **error)
{
GIcon *icon;
GVariant *urgency;
GVariant *ret;
GVariantBuilder builder;
NotifyNotificationPrivate *priv = notification->priv;
GError *local_error = NULL;
static guint32 portal_notification_count = 0;
char *notification_id;
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&builder, "{sv}", "title",
g_variant_new_string (priv->summary ? priv->summary : ""));
g_variant_builder_add (&builder, "{sv}", "body",
g_variant_new_string (priv->body ? priv->body : ""));
if (g_hash_table_lookup (priv->action_map, "default")) {
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("default"));
} else if (g_hash_table_lookup (priv->action_map, "DEFAULT")) {
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("DEFAULT"));
} else if (_notify_get_snap_app ()) {
/* In the snap case we may need to ensure that a default-action
* is set to ensure that we will use the FDO notification daemon
* and won't fallback to GTK one, as app-id won't match.
* See: https://github.com/flatpak/xdg-desktop-portal/issues/769
*/
g_variant_builder_add (&builder, "{sv}", "default-action",
g_variant_new_string ("snap-fake-default-action"));
}
if (priv->has_nondefault_actions) {
GVariantBuilder buttons;
GSList *l;
g_variant_builder_init (&buttons, G_VARIANT_TYPE ("aa{sv}"));
for (l = priv->actions; l && l->next; l = l->next->next) {
GVariantBuilder button;
const char *action;
const char *label;
g_variant_builder_init (&button, G_VARIANT_TYPE_VARDICT);
action = l->data;
label = l->next->data;
g_variant_builder_add (&button, "{sv}", "action",
g_variant_new_string (action));
g_variant_builder_add (&button, "{sv}", "label",
g_variant_new_string (label));
g_variant_builder_add (&buttons, "@a{sv}",
g_variant_builder_end (&button));
}
g_variant_builder_add (&builder, "{sv}", "buttons",
g_variant_builder_end (&buttons));
}
urgency = g_hash_table_lookup (notification->priv->hints, "urgency");
if (urgency) {
switch (g_variant_get_byte (urgency)) {
case NOTIFY_URGENCY_LOW:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("low"));
break;
case NOTIFY_URGENCY_NORMAL:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("normal"));
break;
case NOTIFY_URGENCY_CRITICAL:
g_variant_builder_add (&builder, "{sv}", "priority",
g_variant_new_string ("urgent"));
break;
default:
g_warn_if_reached ();
}
}
icon = get_notification_gicon (notification, &local_error);
if (icon) {
GVariant *serialized_icon = g_icon_serialize (icon);
g_variant_builder_add (&builder, "{sv}", "icon",
serialized_icon);
g_variant_unref (serialized_icon);
g_clear_object (&icon);
} else if (local_error) {
g_propagate_error (error, local_error);
return FALSE;
}
if (!priv->id) {
priv->id = ++portal_notification_count;
} else if (priv->closed_reason == NOTIFY_CLOSED_REASON_UNSET) {
remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_UNSET, NULL);
}
notification_id = get_portal_notification_id (notification);
ret = g_dbus_proxy_call_sync (proxy,
"AddNotification",
g_variant_new ("(s@a{sv})",
notification_id,
g_variant_builder_end (&builder)),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
if (priv->portal_timeout_id) {
g_source_remove (priv->portal_timeout_id);
priv->portal_timeout_id = 0;
}
g_free (notification_id);
if (!ret) {
return FALSE;
}
if (priv->timeout > 0) {
priv->portal_timeout_id = g_timeout_add (priv->timeout,
on_portal_timeout,
notification);
}
g_variant_unref (ret);
return TRUE;
} }
/** /**
@ -1068,7 +634,6 @@ notify_notification_show (NotifyNotification *notification,
GHashTableIter iter; GHashTableIter iter;
gpointer key, data; gpointer key, data;
GVariant *result; GVariant *result;
GApplication *application = NULL;
g_return_val_if_fail (notification != NULL, FALSE); g_return_val_if_fail (notification != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE); g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
@ -1092,10 +657,6 @@ notify_notification_show (NotifyNotification *notification,
notification); notification);
} }
if (_notify_uses_portal_notifications ()) {
return add_portal_notification (proxy, notification, error);
}
g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as")); g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("as"));
for (l = priv->actions; l != NULL; l = l->next) { for (l = priv->actions; l != NULL; l = l->next) {
g_variant_builder_add (&actions_builder, "s", l->data); g_variant_builder_add (&actions_builder, "s", l->data);
@ -1107,41 +668,6 @@ notify_notification_show (NotifyNotification *notification,
g_variant_builder_add (&hints_builder, "{sv}", 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 (_notify_get_snap_app () &&
g_hash_table_lookup (priv->hints, "desktop-entry") == NULL) {
gchar *snap_desktop;
snap_desktop = g_strdup_printf ("%s_%s",
_notify_get_snap_name (),
_notify_get_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));
}
if (!_notify_get_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));
}
}
/* TODO: make this nonblocking */ /* TODO: make this nonblocking */
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"Notify", "Notify",
@ -1219,10 +745,6 @@ notify_notification_set_category (NotifyNotification *notification,
g_return_if_fail (notification != NULL); g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("Category")) {
return;
}
if (category != NULL && category[0] != '\0') { if (category != NULL && category[0] != '\0') {
notify_notification_set_hint_string (notification, notify_notification_set_hint_string (notification,
"category", "category",
@ -1300,18 +822,11 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
hint_name = "icon_data"; hint_name = "icon_data";
} }
g_clear_object (&notification->priv->icon_pixbuf);
if (pixbuf == NULL) { if (pixbuf == NULL) {
notify_notification_set_hint (notification, hint_name, NULL); notify_notification_set_hint (notification, hint_name, NULL);
return; return;
} }
if (_notify_uses_portal_notifications ()) {
notification->priv->icon_pixbuf = g_object_ref (pixbuf);
return;
}
g_object_get (pixbuf, g_object_get (pixbuf,
"width", &width, "width", &width,
"height", &height, "height", &height,
@ -1340,20 +855,13 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
notify_notification_set_hint (notification, hint_name, value); notify_notification_set_hint (notification, hint_name, value);
} }
typedef gchar * (*StringParserFunc) (NotifyNotification *, const gchar *);
static GVariant * static GVariant *
get_parsed_variant (NotifyNotification *notification, get_parsed_variant (GVariant *variant,
const char *key, gchar *(*str_parser)(const gchar *))
GVariant *variant,
StringParserFunc str_parser)
{ {
const char *str = g_variant_get_string (variant, NULL); gchar *parsed = str_parser (g_variant_get_string (variant, NULL));
gchar *parsed = str_parser (notification, str);
if (parsed != NULL && g_strcmp0 (str, parsed) != 0) { if (parsed != NULL) {
g_debug ("Hint %s updated in snap environment: '%s' -> '%s'\n",
key, str, parsed);
g_variant_unref (variant); g_variant_unref (variant);
variant = g_variant_new_take_string (parsed); variant = g_variant_new_take_string (parsed);
} }
@ -1362,28 +870,18 @@ get_parsed_variant (NotifyNotification *notification,
} }
static GVariant * static GVariant *
maybe_parse_snap_hint_value (NotifyNotification *notification, maybe_parse_snap_hint_value (const gchar *key,
const gchar *key,
GVariant *value) GVariant *value)
{ {
StringParserFunc parse_func = NULL;
if (!_notify_get_snap_path ())
return value;
if (g_strcmp0 (key, "desktop-entry") == 0) { if (g_strcmp0 (key, "desktop-entry") == 0) {
parse_func = try_prepend_snap_desktop; value = get_parsed_variant (value, try_prepend_desktop);
} else if (g_strcmp0 (key, "image-path") == 0 || } else if (g_strcmp0 (key, "image-path") == 0 ||
g_strcmp0 (key, "image_path") == 0 || g_strcmp0 (key, "image_path") == 0 ||
g_strcmp0 (key, "sound-file") == 0) { g_strcmp0 (key, "sound-file") == 0) {
parse_func = try_prepend_snap; value = get_parsed_variant (value, try_prepend_snap);
} }
if (parse_func == NULL) {
return value; return value;
}
return get_parsed_variant (notification, key, value, parse_func);
} }
/** /**
@ -1408,7 +906,7 @@ notify_notification_set_hint (NotifyNotification *notification,
g_return_if_fail (key != NULL && *key != '\0'); g_return_if_fail (key != NULL && *key != '\0');
if (value != NULL) { if (value != NULL) {
value = maybe_parse_snap_hint_value (notification, key, value); value = maybe_parse_snap_hint_value (key, value);
g_hash_table_insert (notification->priv->hints, g_hash_table_insert (notification->priv->hints,
g_strdup (key), g_strdup (key),
g_variant_ref_sink (value)); g_variant_ref_sink (value));
@ -1435,10 +933,6 @@ notify_notification_set_app_name (NotifyNotification *notification,
{ {
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification)); g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("App Name")) {
return;
}
g_free (notification->priv->app_name); g_free (notification->priv->app_name);
notification->priv->app_name = g_strdup (app_name); notification->priv->app_name = g_strdup (app_name);
@ -1544,11 +1038,7 @@ notify_notification_set_hint_byte_array (NotifyNotification *notification,
g_return_if_fail (value != NULL || len == 0); 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); value_dup = g_memdup (value, len);
#endif
notify_notification_set_hint (notification, key, notify_notification_set_hint (notification, key,
g_variant_new_from_data (G_VARIANT_TYPE ("ay"), g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
value_dup, value_dup,
@ -1677,28 +1167,6 @@ notify_notification_add_action (NotifyNotification *notification,
} }
} }
/**
* notify_notification_get_activation_token:
* @notification: The notification.
*
* 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);
g_return_val_if_fail (notification->priv->activating, NULL);
return notification->priv->activation_token;
}
gboolean gboolean
_notify_notification_has_nondefault_actions (const NotifyNotification *n) _notify_notification_has_nondefault_actions (const NotifyNotification *n)
{ {
@ -1735,12 +1203,6 @@ notify_notification_close (NotifyNotification *notification,
return FALSE; return FALSE;
} }
if (_notify_uses_portal_notifications ()) {
return remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_API_REQUEST,
error);
}
/* FIXME: make this nonblocking! */ /* FIXME: make this nonblocking! */
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"CloseNotification", "CloseNotification",
@ -1765,17 +1227,13 @@ notify_notification_close (NotifyNotification *notification,
* Returns the closed reason code for the notification. This is valid only * Returns the closed reason code for the notification. This is valid only
* after the "closed" signal is emitted. * after the "closed" signal is emitted.
* *
* Since version 0.8.0 the returned value is of type #NotifyClosedReason. * Returns: The closed reason code.
*
* Returns: An integer representing the closed reason code
* (Since 0.8.0 it's also a #NotifyClosedReason).
*/ */
gint gint
notify_notification_get_closed_reason (const NotifyNotification *notification) notify_notification_get_closed_reason (const NotifyNotification *notification)
{ {
g_return_val_if_fail (notification != NULL, NOTIFY_CLOSED_REASON_UNSET); g_return_val_if_fail (notification != NULL, -1);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), -1);
NOTIFY_CLOSED_REASON_UNSET);
return notification->priv->closed_reason; return notification->priv->closed_reason;
} }

View File

@ -55,11 +55,6 @@ typedef struct _NotifyNotification NotifyNotification;
typedef struct _NotifyNotificationClass NotifyNotificationClass; typedef struct _NotifyNotificationClass NotifyNotificationClass;
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate; typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;
/**
* NotifyNotification:
*
* A passive pop-up notification.
*/
struct _NotifyNotification struct _NotifyNotification
{ {
/*< private >*/ /*< private >*/
@ -93,34 +88,11 @@ typedef enum
} NotifyUrgency; } NotifyUrgency;
/**
* NotifyClosedReason:
* @NOTIFY_CLOSED_REASON_UNSET: Notification not closed.
* @NOTIFY_CLOSED_REASON_EXPIRED: Timeout has expired.
* @NOTIFY_CLOSED_REASON_DISMISSED: It has been dismissed by the user.
* @NOTIFY_CLOSED_REASON_API_REQUEST: It has been closed by a call to
* notify_notification_close().
* @NOTIFY_CLOSED_REASON_UNDEFIEND: Closed by undefined/reserved reasons.
*
* The reason for which the notification has been closed.
*
* Since: 0.8.0
*/
typedef enum
{
NOTIFY_CLOSED_REASON_UNSET = -1,
NOTIFY_CLOSED_REASON_EXPIRED = 1,
NOTIFY_CLOSED_REASON_DISMISSED = 2,
NOTIFY_CLOSED_REASON_API_REQUEST = 3,
NOTIFY_CLOSED_REASON_UNDEFIEND = 4,
} NotifyClosedReason;
/** /**
* NotifyActionCallback: * NotifyActionCallback:
* @notification: a #NotifyActionCallback notification * @notification:
* @action: (transfer none): The activated action name * @action:
* @user_data: (nullable) (transfer none): User provided data * @user_data:
* *
* An action callback function. * An action callback function.
*/ */
@ -208,8 +180,6 @@ void notify_notification_add_action (NotifyNotificatio
gpointer user_data, gpointer user_data,
GFreeFunc free_func); GFreeFunc free_func);
const char *notify_notification_get_activation_token (NotifyNotification *notification);
void notify_notification_clear_actions (NotifyNotification *notification); void notify_notification_clear_actions (NotifyNotification *notification);
gboolean notify_notification_close (NotifyNotification *notification, gboolean notify_notification_close (NotifyNotification *notification,
GError **error); GError **error);

View File

@ -43,14 +43,10 @@
static gboolean _initted = FALSE; static gboolean _initted = FALSE;
static char *_app_name = NULL; static char *_app_name = NULL;
static char *_snap_name = NULL;
static char *_snap_app = NULL;
static char *_flatpak_app = NULL;
static GDBusProxy *_proxy = NULL; static GDBusProxy *_proxy = NULL;
static GList *_active_notifications = NULL; static GList *_active_notifications = NULL;
static int _spec_version_major = 0; static int _spec_version_major = 0;
static int _spec_version_minor = 0; static int _spec_version_minor = 0;
static int _portal_version = 0;
gboolean gboolean
_notify_check_spec_version (int major, _notify_check_spec_version (int major,
@ -78,26 +74,6 @@ _notify_get_server_info (char **ret_name,
return FALSE; return FALSE;
} }
if (_notify_uses_portal_notifications ()) {
if (ret_name) {
*ret_name = g_strdup ("Portal Notification");
}
if (ret_vendor) {
*ret_vendor = g_strdup ("Freedesktop");
}
if (ret_version) {
*ret_version = g_strdup_printf ("%u", _portal_version);
}
if (ret_spec_version) {
*ret_spec_version = g_strdup ("1.2");
}
return TRUE;
}
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"GetServerInformation", "GetServerInformation",
g_variant_new ("()"), g_variant_new ("()"),
@ -143,18 +119,6 @@ _notify_update_spec_version (GError **error)
return TRUE; return TRUE;
} }
static gboolean
set_app_name (const char *app_name)
{
g_return_val_if_fail (app_name != NULL, FALSE);
g_return_val_if_fail (*app_name != '\0', FALSE);
g_free (_app_name);
_app_name = g_strdup (app_name);
return TRUE;
}
/** /**
* notify_set_app_name: * notify_set_app_name:
@ -166,267 +130,38 @@ set_app_name (const char *app_name)
void void
notify_set_app_name (const char *app_name) notify_set_app_name (const char *app_name)
{ {
set_app_name (app_name); g_free (_app_name);
_app_name = g_strdup (app_name);
} }
/** /**
* notify_init: * notify_init:
* @app_name: (nullable): The name of the application initializing libnotify. * @app_name: The name of the application initializing libnotify.
* *
* Initialized libnotify. This must be called before any other functions. * Initialized libnotify. This must be called before any other functions.
* *
* Starting from 0.8, if the provided @app_name is %NULL, libnotify will
* try to figure it out from the running application.
* Before it was not allowed, and was causing libnotify not to be initialized.
*
* Returns: %TRUE if successful, or %FALSE on error. * Returns: %TRUE if successful, or %FALSE on error.
*/ */
gboolean gboolean
notify_init (const char *app_name) notify_init (const char *app_name)
{ {
g_return_val_if_fail (app_name != NULL, FALSE);
g_return_val_if_fail (*app_name != '\0', FALSE);
if (_initted) if (_initted)
return TRUE; return TRUE;
if (app_name == NULL) { notify_set_app_name (app_name);
GApplication *application;
app_name = _notify_get_snap_app (); #if !GLIB_CHECK_VERSION (2, 36, 0)
if (app_name == NULL) { g_type_init ();
app_name = _notify_get_flatpak_app (); #endif
}
if (app_name == NULL &&
(application = g_application_get_default ())) {
app_name = g_application_get_application_id (application);
}
}
if (!set_app_name (app_name)) {
return FALSE;
}
_initted = TRUE; _initted = TRUE;
return TRUE; return TRUE;
} }
static void
_initialize_snap_names (void)
{
gchar *cgroup_contents = NULL;
gchar *found_snap_name = NULL;
gchar **lines;
gint i;
if (!g_file_get_contents ("/proc/self/cgroup", &cgroup_contents,
NULL, NULL)) {
g_free (cgroup_contents);
return;
}
lines = g_strsplit (cgroup_contents, "\n", -1);
g_free (cgroup_contents);
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 (_snap_name == NULL) {
g_free (found_snap_name);
found_snap_name = g_strdup (ns[1]);
}
if (ns_length < 3) {
g_strfreev (ns);
continue;
}
if (_snap_name == NULL) {
_snap_name = found_snap_name;
found_snap_name = NULL;
g_debug ("SNAP name: %s", _snap_name);
}
if (g_str_equal (ns[1], _snap_name)) {
_snap_app = g_strdup (ns[2]);
g_strfreev (ns);
break;
}
g_strfreev (ns);
}
if (_snap_name == NULL && found_snap_name != NULL) {
_snap_name = found_snap_name;
found_snap_name = NULL;
g_debug ("SNAP name: %s", _snap_name);
}
if (_snap_app == NULL) {
_snap_app = g_strdup (_snap_name);
}
g_debug ("SNAP app: %s", _snap_app);
g_strfreev (lines);
g_free (found_snap_name);
}
const char *
_notify_get_snap_path (void)
{
static const char *snap_path = NULL;
static gsize snap_path_set = FALSE;
if (g_once_init_enter (&snap_path_set)) {
snap_path = g_getenv ("SNAP");
if (!snap_path || *snap_path == '\0' ||
!strchr (snap_path, G_DIR_SEPARATOR)) {
snap_path = NULL;
} else {
g_debug ("SNAP path: %s", snap_path);
}
g_once_init_leave (&snap_path_set, TRUE);
}
return snap_path;
}
const char *
_notify_get_snap_name (void)
{
static gsize snap_name_set = FALSE;
if (g_once_init_enter (&snap_name_set)) {
if (!_snap_name) {
const char *snap_name_env = g_getenv ("SNAP_NAME");
if (!snap_name_env || *snap_name_env == '\0')
snap_name_env = NULL;
_snap_name = g_strdup (snap_name_env);
g_debug ("SNAP name: %s", _snap_name);
}
g_once_init_leave (&snap_name_set, TRUE);
}
return _snap_name;
}
const char *
_notify_get_snap_app (void)
{
static gsize snap_app_set = FALSE;
if (g_once_init_enter (&snap_app_set)) {
_initialize_snap_names ();
g_once_init_leave (&snap_app_set, TRUE);
}
return _snap_app;
}
const char *
_notify_get_flatpak_app (void)
{
static gsize flatpak_app_set = FALSE;
if (g_once_init_enter (&flatpak_app_set)) {
GKeyFile *info = g_key_file_new ();
if (g_key_file_load_from_file (info, "/.flatpak-info",
G_KEY_FILE_NONE, NULL)) {
const char *group = "Application";
if (g_key_file_has_group (info, "Runtime")) {
group = "Runtime";
}
_flatpak_app = g_key_file_get_string (info, group,
"name", NULL);
}
g_key_file_free (info);
g_once_init_leave (&flatpak_app_set, TRUE);
}
return _flatpak_app;
}
static gboolean
_notify_is_running_under_flatpak (void)
{
return !!_notify_get_flatpak_app ();
}
static gboolean
_notify_is_running_under_snap (void)
{
return !!_notify_get_snap_app ();
}
static gboolean
_notify_is_running_in_sandbox (void)
{
static gsize use_portal = 0;
enum {
IGNORE_PORTAL = 1,
TRY_USE_PORTAL = 2,
FORCE_PORTAL = 3
};
if (g_once_init_enter (&use_portal)) {
if (G_UNLIKELY (g_getenv ("NOTIFY_IGNORE_PORTAL"))) {
g_once_init_leave (&use_portal, IGNORE_PORTAL);
} else if (G_UNLIKELY (g_getenv ("NOTIFY_FORCE_PORTAL"))) {
g_once_init_leave (&use_portal, FORCE_PORTAL);
} else {
g_once_init_leave (&use_portal, TRY_USE_PORTAL);
}
}
if (use_portal == IGNORE_PORTAL) {
return FALSE;
}
return use_portal == FORCE_PORTAL ||
_notify_is_running_under_flatpak () ||
_notify_is_running_under_snap ();
}
gboolean
_notify_uses_portal_notifications (void)
{
return _portal_version != 0;
}
/** /**
* notify_get_app_name: * notify_get_app_name:
* *
@ -476,15 +211,6 @@ notify_uninit (void)
_proxy = NULL; _proxy = NULL;
} }
g_free (_snap_name);
_snap_name = NULL;
g_free (_snap_app);
_snap_app = NULL;
g_free (_flatpak_app);
_flatpak_app = NULL;
_initted = FALSE; _initted = FALSE;
} }
@ -501,46 +227,6 @@ notify_is_initted (void)
return _initted; return _initted;
} }
GDBusProxy *
_get_portal_proxy (GError **error)
{
GError *local_error = NULL;
GDBusProxy *proxy;
GVariant *res;
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
NOTIFY_PORTAL_DBUS_NAME,
NOTIFY_PORTAL_DBUS_CORE_OBJECT,
NOTIFY_PORTAL_DBUS_CORE_INTERFACE,
NULL,
&local_error);
if (proxy == NULL) {
g_debug ("Failed to get portal proxy: %s", local_error->message);
g_clear_error (&local_error);
return NULL;
}
res = g_dbus_proxy_get_cached_property (proxy, "version");
if (!res) {
g_object_unref (proxy);
return NULL;
}
_portal_version = g_variant_get_uint32 (res);
g_assert (_portal_version > 0);
g_warning ("Running in confined mode, using Portal notifications. "
"Some features and hints won't be supported");
g_variant_unref (res);
return proxy;
}
/* /*
* _notify_get_proxy: * _notify_get_proxy:
* @error: (allow-none): a location to store a #GError, or %NULL * @error: (allow-none): a location to store a #GError, or %NULL
@ -556,14 +242,6 @@ _notify_get_proxy (GError **error)
if (_proxy != NULL) if (_proxy != NULL)
return _proxy; return _proxy;
if (_notify_is_running_in_sandbox ()) {
_proxy = _get_portal_proxy (error);
if (_proxy != NULL) {
goto out;
}
}
_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, _proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL, NULL,
@ -572,8 +250,6 @@ _notify_get_proxy (GError **error)
NOTIFY_DBUS_CORE_INTERFACE, NOTIFY_DBUS_CORE_INTERFACE,
NULL, NULL,
error); error);
out:
if (_proxy == NULL) { if (_proxy == NULL) {
return NULL; return NULL;
} }
@ -611,15 +287,6 @@ notify_get_server_caps (void)
return NULL; return NULL;
} }
if (_notify_uses_portal_notifications ()) {
list = g_list_prepend (list, g_strdup ("actions"));
list = g_list_prepend (list, g_strdup ("body"));
list = g_list_prepend (list, g_strdup ("body-images"));
list = g_list_prepend (list, g_strdup ("icon-static"));
return list;
}
result = g_dbus_proxy_call_sync (proxy, result = g_dbus_proxy_call_sync (proxy,
"GetCapabilities", "GetCapabilities",
g_variant_new ("()"), g_variant_new ("()"),

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/freedesktop/Notification">
<interface name="org.freedesktop.NotificationListener">
</interface>
</node>

View File

@ -1,6 +1,6 @@
project('libnotify', project('libnotify',
'c', 'c',
version: '0.8.1', version: '0.7.9',
meson_version: '>= 0.47.0') meson_version: '>= 0.47.0')
gnome = import('gnome') gnome = import('gnome')
@ -22,11 +22,8 @@ LT_CURRENT=4
LT_REVISION=0 LT_REVISION=0
LT_AGE=0 LT_AGE=0
API_VERSION = 7
VERSION_ARRAY = meson.project_version().split('.') VERSION_ARRAY = meson.project_version().split('.')
# Minor version is hardcoded until we have a real API break MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], VERSION_ARRAY[1])
MODULE_VERSION = '@0@.@1@'.format(VERSION_ARRAY[0], API_VERSION)
LIBNAME = meson.project_name().split('lib')[1] LIBNAME = meson.project_name().split('lib')[1]
default_includes = include_directories('.') default_includes = include_directories('.')
@ -39,7 +36,7 @@ man1dir = join_paths(prefix, get_option('mandir'), 'man1')
libnotify_deps = [] libnotify_deps = []
extra_deps = [] extra_deps = []
glib_req_version = '>= 2.38.0' glib_req_version = '>= 2.26.0'
gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0')
glib_dep = dependency('glib-2.0', version: glib_req_version) glib_dep = dependency('glib-2.0', version: glib_req_version)
@ -69,7 +66,7 @@ if get_option('man')
'@INPUT@', '@INPUT@',
] ]
testrun = run_command(xsltproc, '--nonet', stylesheet, check: false) testrun = run_command(xsltproc, '--nonet', stylesheet)
if testrun.returncode() != 0 if testrun.returncode() != 0
error('DocBook stylesheet for generating man pages not found, you need to install docbook-xsl-ns or similar package.') error('DocBook stylesheet for generating man pages not found, you need to install docbook-xsl-ns or similar package.')

View File

@ -29,6 +29,10 @@ main ()
{ {
NotifyNotification *n; NotifyNotification *n;
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
notify_init ("Error Handling"); notify_init ("Error Handling");
n = notify_notification_new ("Summary", "Content", NULL); n = notify_notification_new ("Summary", "Content", NULL);

View File

@ -28,6 +28,10 @@ main ()
GError *error; GError *error;
error = NULL; error = NULL;
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
notify_init ("Replace Test"); notify_init ("Replace Test");
n = notify_notification_new ("Summary", n = notify_notification_new ("Summary",

View File

@ -26,14 +26,12 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <glib.h> #include <glib.h>
#include <glib-unix.h>
#include <glib/gprintf.h> #include <glib/gprintf.h>
#define N_(x) (x) #define N_(x) (x)
#define GETTEXT_PACKAGE NULL #define GETTEXT_PACKAGE NULL
static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL; static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
static GMainLoop *loop = NULL;
static gboolean static gboolean
g_option_arg_urgency_cb (const char *option_name, g_option_arg_urgency_cb (const char *option_name,
@ -101,26 +99,6 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
key, key,
(guchar) h_byte); (guchar) h_byte);
} }
} else if (g_ascii_strcasecmp (type, "boolean") == 0) {
gboolean h_boolean = FALSE;
if (g_ascii_strcasecmp (value, "true") == 0) {
h_boolean = TRUE;
} else if (g_ascii_isdigit (*value)) {
h_boolean = !!g_ascii_strtoull (value, NULL, 10);
}
notify_notification_set_hint (notification, key,
g_variant_new_boolean (h_boolean));
} else if (g_ascii_strcasecmp (type, "variant") == 0) {
GVariant *variant = g_variant_parse (NULL, value, NULL, NULL, NULL);
if (variant != NULL) {
notify_notification_set_hint (notification, key, variant);
} else {
conv_error = TRUE;
}
} else { } else {
*error = g_error_new (G_OPTION_ERROR, *error = g_error_new (G_OPTION_ERROR,
G_OPTION_ERROR_BAD_VALUE, G_OPTION_ERROR_BAD_VALUE,
@ -142,70 +120,6 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
return TRUE; return TRUE;
} }
static void
handle_closed (NotifyNotification *notify,
gpointer user_data)
{
g_main_loop_quit (loop);
}
static gboolean
on_sigint (gpointer data)
{
NotifyNotification *notification = data;
g_printerr ("Wait cancelled, closing notification\n");
notify_notification_close (notification, NULL);
g_main_loop_quit (loop);
return FALSE;
}
static void
handle_action (NotifyNotification *notify,
char *action,
gpointer user_data)
{
const char *action_name = user_data;
const char *activation_token;
activation_token = notify_notification_get_activation_token (notify);
g_printf ("%s\n", action_name);
if (activation_token) {
g_debug ("Activation Token: %s", activation_token);
}
notify_notification_close (notify, NULL);
}
static gboolean
on_wait_timeout (gpointer data)
{
fprintf (stderr, "Wait timeout expired\n");
g_main_loop_quit (loop);
return FALSE;
}
static gboolean
server_has_capability (const gchar *capability)
{
GList *server_caps = notify_get_server_caps ();
gboolean supported;
supported = !!g_list_find_custom (server_caps,
capability,
(GCompareFunc) g_ascii_strcasecmp);
g_list_foreach (server_caps, (GFunc) g_free, NULL);
g_list_free (server_caps);
return supported;
}
int int
main (int argc, char *argv[]) main (int argc, char *argv[])
{ {
@ -216,17 +130,8 @@ main (int argc, char *argv[])
static char *icon_str = NULL; static char *icon_str = NULL;
static char **n_text = NULL; static char **n_text = NULL;
static char **hints = NULL; static char **hints = NULL;
static char **actions = NULL;
static char *server_name = NULL;
static char *server_vendor = NULL;
static char *server_version = NULL;
static char *server_spec_version = NULL;
static gboolean print_id = FALSE;
static gint notification_id = 0;
static gboolean do_version = FALSE; static gboolean do_version = FALSE;
static gboolean hint_error = FALSE, show_error = FALSE; static gboolean hint_error = FALSE, show_error = FALSE;
static gboolean transient = FALSE;
static gboolean wait = FALSE;
static glong expire_timeout = NOTIFY_EXPIRES_DEFAULT; static glong expire_timeout = NOTIFY_EXPIRES_DEFAULT;
GOptionContext *opt_ctx; GOptionContext *opt_ctx;
NotifyNotification *notify; NotifyNotification *notify;
@ -250,26 +155,10 @@ main (int argc, char *argv[])
{"category", 'c', 0, G_OPTION_ARG_FILENAME, &type, {"category", 'c', 0, G_OPTION_ARG_FILENAME, &type,
N_("Specifies the notification category."), N_("Specifies the notification category."),
N_("TYPE[,TYPE...]")}, N_("TYPE[,TYPE...]")},
{"transient", 'e', 0, G_OPTION_ARG_NONE, &transient,
N_("Create a transient notification"),
NULL},
{"hint", 'h', 0, G_OPTION_ARG_FILENAME_ARRAY, &hints, {"hint", 'h', 0, G_OPTION_ARG_FILENAME_ARRAY, &hints,
N_ N_
("Specifies basic extra data to pass. Valid types are boolean, int, double, string, byte and variant."), ("Specifies basic extra data to pass. Valid types are int, double, string and byte."),
N_("TYPE:NAME:VALUE")}, N_("TYPE:NAME:VALUE")},
{"print-id", 'p', 0, G_OPTION_ARG_NONE, &print_id,
N_ ("Print the notification ID."), NULL},
{"replace-id", 'r', 0, G_OPTION_ARG_INT, &notification_id,
N_ ("The ID of the notification to replace."), N_("REPLACE_ID")},
{"wait", 'w', 0, G_OPTION_ARG_NONE, &wait,
N_("Wait for the notification to be closed before exiting."),
NULL},
{"action", 'A', 0, G_OPTION_ARG_FILENAME_ARRAY, &actions,
N_
("Specifies the actions to display to the user. Implies --wait to wait for user input."
" May be set multiple times. The name of the action is output to stdout. If NAME is "
"not specified, the numerical index of the option is used (starting with 0)."),
N_("[NAME=]Text...")},
{"version", 'v', 0, G_OPTION_ARG_NONE, &do_version, {"version", 'v', 0, G_OPTION_ARG_NONE, &do_version,
N_("Version of the package."), N_("Version of the package."),
NULL}, NULL},
@ -283,6 +172,10 @@ main (int argc, char *argv[])
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
g_set_prgname (argv[0]); g_set_prgname (argv[0]);
g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
@ -324,41 +217,14 @@ main (int argc, char *argv[])
if (!notify_init ("notify-send")) if (!notify_init ("notify-send"))
exit (1); exit (1);
notify_get_server_info (&server_name, notify = notify_notification_new (summary,
&server_vendor, body,
&server_version, icon_str);
&server_spec_version);
g_debug ("Using sever %s %s, v%s - Supporting Notification Spec %s",
server_name, server_vendor, server_version, server_spec_version);
g_free (server_name);
g_free (server_vendor);
g_free (server_version);
g_free (server_spec_version);
notify = g_object_new (NOTIFY_TYPE_NOTIFICATION,
"summary", summary,
"body", body,
"icon-name", icon_str,
"id", notification_id,
NULL);
notify_notification_set_category (notify, type); notify_notification_set_category (notify, type);
notify_notification_set_urgency (notify, urgency); notify_notification_set_urgency (notify, urgency);
notify_notification_set_timeout (notify, expire_timeout); notify_notification_set_timeout (notify, expire_timeout);
notify_notification_set_app_name (notify, app_name); notify_notification_set_app_name (notify, app_name);
if (transient) {
notify_notification_set_hint (notify, "transient",
g_variant_new_boolean (TRUE));
if (!server_has_capability ("persistence")) {
g_debug ("Persistence is not supported by the "
"notifications server. "
"All notifications are transient.");
}
}
g_free (body); g_free (body);
/* Set hints */ /* Set hints */
@ -386,7 +252,7 @@ main (int argc, char *argv[])
if (!retval) { if (!retval) {
fprintf (stderr, "%s\n", error->message); fprintf (stderr, "%s\n", error->message);
g_clear_error (&error); g_error_free (error);
hint_error = TRUE; hint_error = TRUE;
} }
} }
@ -397,84 +263,16 @@ main (int argc, char *argv[])
} }
} }
if (actions != NULL) {
gint i = 0;
char *action = NULL;
gchar **spl = NULL;
gboolean have_actions;
have_actions = server_has_capability ("actions");
if (!have_actions) {
g_printerr (N_("Actions are not supported by this "
"notifications server. "
"Displaying non-interactively.\n"));
show_error = TRUE;
}
while (have_actions && (action = actions[i++])) {
gchar *name;
const gchar *label;
spl = g_strsplit (action, "=", 2);
if (g_strv_length (spl) == 1) {
name = g_strdup_printf ("%d", i - 1);
label = g_strstrip (spl[0]);
} else {
name = g_strdup (g_strstrip (spl[0]));
label = g_strstrip (spl[1]);
}
if (*label != '\0' && *name != '\0') {
notify_notification_add_action (notify,
name,
label,
handle_action,
name,
g_free);
wait = TRUE;
}
g_strfreev (spl);
}
g_strfreev (actions);
}
if (wait) {
g_signal_connect (G_OBJECT (notify),
"closed",
G_CALLBACK (handle_closed),
NULL);
if (expire_timeout > 0) {
g_timeout_add (expire_timeout, on_wait_timeout, NULL);
}
}
if (!hint_error) { if (!hint_error) {
retval = notify_notification_show (notify, &error); retval = notify_notification_show (notify, &error);
if (!retval) { if (!retval) {
fprintf (stderr, "%s\n", error->message); fprintf (stderr, "%s\n", error->message);
g_clear_error (&error); g_error_free (error);
show_error = TRUE; show_error = TRUE;
} }
} }
if (print_id) {
g_object_get (notify, "id", &notification_id, NULL);
g_printf ("%d\n", notification_id);
}
if (wait) {
g_unix_signal_add (SIGINT, on_sigint, notify);
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
g_main_loop_unref (loop);
loop = NULL;
}
g_object_unref (G_OBJECT (notify)); g_object_unref (G_OBJECT (notify));
notify_uninit (); notify_uninit ();