Compare commits

..

64 Commits

Author SHA1 Message Date
Andre Klapper 80121a3b8c docs: Fix URL to Desktop Notification Spec
Fixes #29
2022-09-04 11:59:49 +02:00
Marco Trevisan (Treviño) 650f2f123e
Releasing version 0.8.1
Update NEWS.
2022-07-17 15:00:39 +02:00
Marco Trevisan (Treviño) a8374698c4 build: Hardcode API version to 0.7
Fixes: #27
2022-07-17 14:58:13 +02:00
Marco Trevisan (Treviño) 359443f70f
Releasing version 0.8.0
Update NEWS.
2022-07-14 17:18:41 +02:00
Marco Trevisan (Treviño) 3d81394cd2 NEWS: Fix style on previous announcement 2022-07-14 17:18:25 +02:00
Marco Trevisan (Treviño) 5e6a52fa4f README: Add reference to Portal Notification APIs 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 532608248e build: Pre-release bump to 0.8.0 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) adb3e0c7bf notification: Warn if trying to get the activation token outside activation
This API is intended to be used only during a NotifyActionCallback, so
ensure that this is the case, by warn in case it's not the case.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 13718b6ea0 notify: Also try to use the flatpak app name as notify app 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) f3eb807464 notification: Use Desktop Portal notification when running in sandbox
When an application is running from a snap or a flatpak, libnotify
should just work as a FDO Portal Notification wrapper.

As per this, make the library to try use the portal API in sandboxed
applications, by emulating the needed missing pieces.

Not everything can be achieved 1:1, but it's just safer to use in such
contexts.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) c6534a144c build: Bump dependency on glib 2.38
It's almost 10 years old, so I think we can safely depend on that to
support the features we're optionally depending on, plus the ones we're
going to add
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 543d636153 notification: Move action activation and closing in separate functions
Move handling of such events in different functions so that can be
re-used
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 9e9cb423b9 Notification: Add NotifyClosedReason enum to define closed reasons
We don't change the formal type of notify_notification_get_closed_reason
but it's safe enough to compare the two returned values.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) cc30d8b8dd notify: Support passing a NULL application name to notify_init()
We can find its name in some scenarios, so make it clearer from in the API.

Also, ensure that notify_set_app_name() is not used with a NULL App
name.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) e8ea0c39d0 notify: Use lazy snap readings and once per notify instance
An application under snap confinement won't have a way of escaping from
it, so we can just initialize the snap variables at the global notify
scope once and just re-use them when needed.
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 74f87250a3 notify-send: Handle Ctrl+C by closing the notification before exiting
Avoid leaving notifications we're waiting for around on SIGNINT
2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 07a2b2e32c notify-send: Add debug logs about the notification daemon 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 8ce5daac4f notification: Add docs description for NotifyNotification 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 9d28fa3600 notification: Document get_activation_token argument 2022-06-07 03:18:38 +02:00
Marco Trevisan (Treviño) 4e40a460ce notification: Fix documentation and transfer info for NotifyActionCallback 2022-06-07 03:18:38 +02:00
Logan Rathbone 8162b1d397 Bump version to 0.7.12
Update NEWS.
2022-05-05 12:03:27 -04:00
Logan Rathbone 4d68baf5ef Merge branch 'fix-electron-crash' into 'master'
Closes #25

See merge request GNOME/libnotify!26
2022-05-05 02:10:25 +00:00
Logan Rathbone 596d09a78f notification: Bookend calling NotifyActionCallback with temporary ref
Starting with d0778595, we access the Notification object after the
NotifyActionCallback; some clients (eg, Electron) have historically
unref'd the object at the end of the NotifyActionCallback. They probably
should not have been doing so, but this hotfix adds an additional ref
before and after the callback so that we don't break existing code.

Fixes #25
2022-05-04 21:04:25 -04:00
Logan Rathbone f5b9619b2f doap: Add self as co-maintainer; remove chammond and mccann
Per:
- Discussions on #release-team
- No response to email to mccann in over 10 days
- Email response from chammond confirming he should no longer be listed
  as maintainer

See also: afef1e98
Closes: #14
2022-05-04 11:36:37 -04:00
Marco Trevisan (Treviño) afef1e98b3 Adding myself as maintainer
As discussed on #release-team:

  21:12:18 <ebassi> Trevinho: You could already add yourself to the DOAP file
2022-05-04 16:22:23 +02:00
Marco Trevisan (Treviño) ecb1d4ec08 libnotify.doap: Add missing description field 2022-05-04 16:22:23 +02:00
Patrick Griffis 6d8e38bc69 Merge branch 'pgriffis/delete-unused-file' into 'master'
Delete unused notifynotification.xml

See merge request GNOME/libnotify!25
2022-05-03 20:37:41 +00:00
Patrick Griffis 6e6d52340b Delete unused notifynotification.xml 2022-05-03 20:35:50 +00:00
Marco Trevisan (Treviño) 1fba04bc03 notification: Include sender-pid hint by default if not provided
It's used by various daemons including GNOME Shell to figure out the
parent application.
2022-04-28 01:06:58 +02:00
Marco Trevisan (Treviño) a674e610ee notify-send: Add debug message about server not supporting persistence
In case a transient notification is requested but persistence is not
supported, it's not an error but it is useful to show a debug message
to clarify that.
2022-04-27 21:32:56 +02:00
Marco Trevisan (Treviño) c99aacdb91 notify-send: Move server capabilities check to a separate function 2022-04-27 21:32:30 +02:00
Marco Trevisan (Treviño) 997115b459 docs/notify-send: Add --transient option to manpage 2022-04-27 21:25:10 +02:00
Marco Trevisan (Treviño) e8a57f7245
Bump version to 0.7.11
Update NEWS.
2022-04-27 21:05:49 +02:00
Marco Trevisan (Treviño) ed43da3479 notify-send: Add explicit option to create transient notifications 2022-04-27 20:52:48 +02:00
Marco Trevisan (Treviño) 02f2f91c3d notify-send: Support passing any hint value, by parsing variant strings 2022-04-27 20:45:06 +02:00
Marco Trevisan (Treviño) d6f4734adf notify-send: Add support for boolean hints
Boolean hints are used for some common parameters such as "transient".

Original bug: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=636343
2022-04-27 20:33:40 +02:00
Marco Trevisan (Treviño) 6e871a4047 cleanup: Do not use GLIB_CHECK_VERSION
It's not supported by GLib 2.26.0, the minimum version we required
2022-04-27 20:18:48 +02:00
Marco Trevisan (Treviño) 59553a5af2 NEWS: Add --wait feature to last release 2022-04-27 20:16:49 +02:00
Marco Trevisan (Treviño) 2674c76f53
Bump version to 0.7.10
Update NEWS.
2022-04-27 19:00:44 +02:00
Marco Trevisan (Treviño) d138596c78 CI: Also install the built files and expose docs and logs 2022-04-27 18:29:47 +02:00
Marco Trevisan (Treviño) d07785959c notification: Add support for getting actions activation token
Notification actions can now be activated with an activation token
containing platform data. So update the notification-spec to include
latest version of the specification, and expose the activation token
during an action activation.

Not to change the API, by modifying the type of NotifyActionCallback
it's just better to provide a function to fetch the activation token
during activation only, so that this can be retro-compatible.

Reference: https://gitlab.freedesktop.org/xdg/xdg-specs/-/commit/b9a4700
2022-04-27 18:02:20 +02:00
Marco Trevisan (Treviño) 47400a2ff7 notify-send: Clear errors that could be re-used
We're using error variable multiple times, and this may lead to problems
when trying to set it again. So let's just clear once it's used.
2022-04-25 22:23:40 +02:00
Marco Trevisan (Treviño) 1da6996491 notify: Use the application ID if available to set the fallback app name 2022-04-25 22:23:40 +02:00
Ben Blain e05030875e docs/notify-send: Add missing --app-name description to manual 2022-04-25 22:23:40 +02:00
Ben Blain 12d4b90623 notify-send: Check for notification server actions capability support 2022-04-25 22:23:40 +02:00
Ben Blain 6c707d2dfd notify-send: Add support for notification actions and responses
The activated action name is written to stdout and the notification is closed
when a valid action has been selected.
2022-04-25 22:23:37 +02:00
Ben Blain a396dd9af9 notify-send: Add option to wait until notification has been closed
If an expiration timeout is set, the notification is not waited longer
than such time.
2022-04-25 22:15:50 +02:00
Matthias Sweertvaegher 00a7e74774 docs/notify-send: Add print/replace-id arguments to man page 2022-04-25 19:43:12 +02:00
Paul Collins 652b4d6911 notify-send: Support for replacing an existing notification
Support printing and overriding notification ID, so that it's possible
to replace a notification that is currently showing with an updated
content.

Based on patch as provided by Paul Collins in the following bug
report:
  https://bugs.launchpad.net/ubuntu/+source/libnotify/+bug/257135/comments/8

Co-authored-by: Marco Trevisan (Treviño) <mail@3v1n0.net>

Fixes #19
2022-04-25 19:42:13 +02:00
Marco Trevisan (Treviño) f9c1cf1b71 notification: Keep converted URIs as such when we prepend a path 2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) 997e2e374a notification: Do not duplicate calls on parse functions 2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) 0d86d8c8d5 notification: Improve debug logs when rewriting paths 2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) b2d6712b15 notification: Avoid creating a new hint variant when nothing has changed 2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) 961f45f44c notification: Use snap name and app name as fallback desktop-entry
Snaps use desktop IDs based on the snap name and the application name
inside the snap, so if we're in a such container, we need to compute the
desktop-entry using those values. We can figure them out from the snap
cgroup they're in.

In such case, don't even try to use the GApplication ID as that is
likely wrong in a snapped environment.
2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) e72396e8ca notification: Preserve snap details into the private struct
We may need to use them in various places, so there's no point to repeat
the same in multiple places, this way we can also use some shortcuts for
non-snap setups.
2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) d21b8140a8 notification: Do not try to append snap name to desktop when it's a path 2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) c4d5ea8baf notification: Do not prepend SNAP prefix when is already present
We used to just add it again, to eventually figure out that the file
did not exists, but may not work properly in some scenarios.

Also always fill the path_filename when using a file-name only, as that
may be refer to something under the SNAP path, and in such case we want
to try check if that exists before giving up.
2022-04-25 19:13:22 +02:00
Marco Trevisan (Treviño) d348a36a05 build: Do not require g-ir-scanner on introspection=auto 2022-04-21 23:53:44 +02:00
Marco Trevisan (Treviño) d5a888fa31 notification: Use g_memdup2 when available
It's not really an issue here, as we use a properly typed len, but
better not to use a deprecated and potentially dangerous API.
2022-04-21 23:53:44 +02:00
Marco Trevisan (Treviño) 736fcea1d0 build: Avoid warnings or mis-behavior in recent meson versions
In future meson will check the run_command result by default, while it's
fine for us, it would cause our nicer error message not to work.
2022-04-21 23:53:44 +02:00
Marco Trevisan (Treviño) 2accd4ceab Revert "Revert "Merge branch 'readme' into 'master'""
This reverts commit 1ad4630107.

As per: https://gitlab.gnome.org/GNOME/libnotify/-/commit/1ad46301#note_1434358
2022-04-21 18:51:13 +02:00
Corentin Noël 333f7c5d32 Send the application ID when possible 2022-04-21 16:35:38 +02:00
David King 7f31e29e62 docs: Use consistent IDs in spec build
This avoids differences between IDs for separate builds of the
specification.
2022-04-21 14:37:58 +02:00
Logan Rathbone 1ad4630107 Revert "Merge branch 'readme' into 'master'"
This reverts merge request !21
2022-03-21 22:19:32 +00:00
18 changed files with 1406 additions and 102 deletions

View File

@ -26,4 +26,12 @@ build:ubuntu:
- pip3 install meson
script:
- meson _build -Ddocbook_docs=enabled
- ninja -C _build
- ninja -C _build install
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,3 +1,71 @@
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
============
* Fixed linking in darwin [Iain, Marco; !5]

20
README.md Normal file
View File

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

View File

@ -1272,6 +1272,65 @@
</para>
</note>
</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>
</sect1>
</article>

View File

@ -58,6 +58,18 @@
<para>Show help and exit.</para>
</listitem>
</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>
<term><option>-u</option>, <option>--urgency</option>=<replaceable>LEVEL</replaceable></term>
<listitem>
@ -89,7 +101,31 @@
<varlistentry>
<term><option>-h</option>, <option>--hint</option>=<replaceable>TYPE</replaceable>:<replaceable>NAME</replaceable>:<replaceable>VALUE</replaceable> </term>
<listitem>
<para>Specifies basic extra data to pass. Valid types are <literal>INT</literal>, <literal>DOUBLE</literal>, <literal>STRING</literal> and <literal>BYTE</literal>.</para>
<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>
</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>
</varlistentry>
</variablelist>
@ -97,6 +133,6 @@
<refsection>
<info><title>See also</title></info>
<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>
<para>The Desktop Notification Spec on <link xlink:href="https://specifications.freedesktop.org/notification-spec/">https://specifications.freedesktop.org/notification-spec/</link>.</para>
</refsection>
</refentry>

View File

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

View File

@ -8,18 +8,21 @@
<category rdf:resource="http://api.gnome.org/doap-extensions#core" />
<programming-language>C</programming-language>
<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>
<foaf:Person>
<foaf:name>Christian Hammond</foaf:name>
<foaf:mbox rdf:resource="mailto:chipx86@chipx86.com" />
<gnome:userid>chammond</gnome:userid>
<foaf:name>Marco Trevisan</foaf:name>
<foaf:mbox rdf:resource="mailto:mail@3v1n0.net" />
<gnome:userid>marcotrevi</gnome:userid>
</foaf:Person>
</maintainer>
<maintainer>
<foaf:Person>
<foaf:name>William Jon McCann</foaf:name>
<foaf:mbox rdf:resource="mailto:william.jon.mccann@gmail.com" />
<gnome:userid>mccann</gnome:userid>
<foaf:name>Logan Rathbone</foaf:name>
<foaf:mbox rdf:resource="mailto:poprocks@gmail.com" />
<gnome:userid>larathbone</gnome:userid>
</foaf:Person>
</maintainer>
</Project>

View File

@ -26,6 +26,10 @@
#define NOTIFY_DBUS_CORE_INTERFACE "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
GDBusProxy * _notify_get_proxy (GError **error);
@ -36,6 +40,14 @@ gint _notify_notification_get_timeout (const NotifyNotific
gboolean _notify_notification_has_nondefault_actions (const NotifyNotification *n);
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
#endif /* _LIBNOTIFY_INTERNAL_H_ */

View File

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

View File

@ -53,6 +53,7 @@
static void notify_notification_class_init (NotifyNotificationClass *klass);
static void notify_notification_init (NotifyNotification *sp);
static void notify_notification_finalize (GObject *object);
static void notify_notification_dispose (GObject *object);
typedef struct
{
@ -68,9 +69,11 @@ struct _NotifyNotificationPrivate
char *app_name;
char *summary;
char *body;
char *activation_token;
/* NULL to use icon data. Anything else to have server lookup icon */
char *icon_name;
GdkPixbuf *icon_pixbuf;
/*
* -1 = use server default
@ -78,12 +81,14 @@ struct _NotifyNotificationPrivate
* > 0 = Number of milliseconds before we timeout
*/
gint timeout;
guint portal_timeout_id;
GSList *actions;
GHashTable *action_map;
GHashTable *hints;
gboolean has_nondefault_actions;
gboolean activating;
gboolean updates_pending;
gulong proxy_signal_handler;
@ -148,6 +153,7 @@ notify_notification_class_init (NotifyNotificationClass *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->dispose = notify_notification_dispose;
object_class->finalize = notify_notification_finalize;
/**
@ -232,9 +238,9 @@ notify_notification_class_init (NotifyNotificationClass *klass)
g_param_spec_int ("closed-reason",
"Closed Reason",
"The reason code for why the notification was closed",
-1,
NOTIFY_CLOSED_REASON_UNSET,
G_MAXINT32,
-1,
NOTIFY_CLOSED_REASON_UNSET,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME
| G_PARAM_STATIC_NICK
@ -355,7 +361,7 @@ notify_notification_init (NotifyNotification *obj)
{
obj->priv = g_new0 (NotifyNotificationPrivate, 1);
obj->priv->timeout = NOTIFY_EXPIRES_DEFAULT;
obj->priv->closed_reason = -1;
obj->priv->closed_reason = NOTIFY_CLOSED_REASON_UNSET;
obj->priv->hints = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
@ -367,6 +373,22 @@ notify_notification_init (NotifyNotification *obj)
(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
notify_notification_finalize (GObject *object)
{
@ -380,6 +402,7 @@ notify_notification_finalize (GObject *object)
g_free (priv->summary);
g_free (priv->body);
g_free (priv->icon_name);
g_free (priv->activation_token);
if (priv->actions != NULL) {
g_slist_foreach (priv->actions, (GFunc) g_free, NULL);
@ -402,6 +425,18 @@ notify_notification_finalize (GObject *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:
* @summary: The required summary text.
@ -431,30 +466,52 @@ try_prepend_path (const char *base_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);
}
}
}
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_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);
@ -463,33 +520,32 @@ try_prepend_path (const char *base_path,
}
static gchar *
try_prepend_desktop (const gchar *desktop)
try_prepend_snap_desktop (NotifyNotification *notification,
const gchar *desktop)
{
gchar *ret;
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}_; snap .desktop files are in the format
* ${SNAP_NAME}_desktop_file_name
*/
ret = try_prepend_path (desktop, g_getenv ("SNAP"));
ret = try_prepend_path (desktop, _notify_get_snap_path ());
if (ret == NULL) {
const gchar *snap_name = g_getenv ("SNAP_NAME");
if (snap_name != NULL && snap_name[0] != '\0') {
ret = g_strdup_printf ("%s_%s", snap_name, desktop);
}
if (ret == NULL && _notify_get_snap_name () != NULL &&
strchr (desktop, G_DIR_SEPARATOR) == NULL) {
ret = g_strdup_printf ("%s_%s", _notify_get_snap_name (), desktop);
}
return ret;
}
static gchar *
try_prepend_snap (const gchar *value)
try_prepend_snap (NotifyNotification *notification,
const gchar *value)
{
/* hardcoded paths to icons might be relocated under $SNAP */
return try_prepend_path (value, g_getenv ("SNAP"));
return try_prepend_path (value, _notify_get_snap_path ());
}
@ -524,7 +580,8 @@ notify_notification_update_internal (NotifyNotification *notification,
g_free (notification->priv->icon_name);
notification->priv->icon_name = (icon != NULL
&& *icon != '\0' ? g_strdup (icon) : NULL);
snapped_icon = try_prepend_desktop (notification->priv->icon_name);
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);
@ -567,6 +624,80 @@ notify_notification_update (NotifyNotification *notification,
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
proxy_g_signal_cb (GDBusProxy *proxy,
const char *sender_name,
@ -574,8 +705,12 @@ proxy_g_signal_cb (GDBusProxy *proxy,
GVariant *parameters,
NotifyNotification *notification)
{
const char *interface;
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
interface = g_dbus_proxy_get_interface_name (proxy);
if (g_strcmp0 (signal_name, "NotificationClosed") == 0 &&
g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)"))) {
guint32 id, reason;
@ -584,33 +719,332 @@ proxy_g_signal_cb (GDBusProxy *proxy,
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));
close_notification (notification, reason);
} 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)"))) {
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 (!activate_action (notification, action) &&
g_ascii_strcasecmp (action, "default")) {
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;
if (pair == NULL) {
if (g_ascii_strcasecmp (action, "default")) {
g_warning ("Received unknown action %s", action);
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 {
g_debug ("Unhandled signal '%s.%s'", interface, signal_name);
}
}
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;
}
} else {
pair->cb (notification, (char *) action, pair->user_data);
}
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;
}
/**
@ -634,6 +1068,7 @@ notify_notification_show (NotifyNotification *notification,
GHashTableIter iter;
gpointer key, data;
GVariant *result;
GApplication *application = NULL;
g_return_val_if_fail (notification != NULL, FALSE);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification), FALSE);
@ -657,6 +1092,10 @@ notify_notification_show (NotifyNotification *notification,
notification);
}
if (_notify_uses_portal_notifications ()) {
return add_portal_notification (proxy, notification, error);
}
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);
@ -668,6 +1107,41 @@ notify_notification_show (NotifyNotification *notification,
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 */
result = g_dbus_proxy_call_sync (proxy,
"Notify",
@ -745,6 +1219,10 @@ notify_notification_set_category (NotifyNotification *notification,
g_return_if_fail (notification != NULL);
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("Category")) {
return;
}
if (category != NULL && category[0] != '\0') {
notify_notification_set_hint_string (notification,
"category",
@ -822,11 +1300,18 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
hint_name = "icon_data";
}
g_clear_object (&notification->priv->icon_pixbuf);
if (pixbuf == NULL) {
notify_notification_set_hint (notification, hint_name, NULL);
return;
}
if (_notify_uses_portal_notifications ()) {
notification->priv->icon_pixbuf = g_object_ref (pixbuf);
return;
}
g_object_get (pixbuf,
"width", &width,
"height", &height,
@ -855,13 +1340,20 @@ notify_notification_set_image_from_pixbuf (NotifyNotification *notification,
notify_notification_set_hint (notification, hint_name, value);
}
static GVariant *
get_parsed_variant (GVariant *variant,
gchar *(*str_parser)(const gchar *))
{
gchar *parsed = str_parser (g_variant_get_string (variant, NULL));
typedef gchar * (*StringParserFunc) (NotifyNotification *, const gchar *);
if (parsed != NULL) {
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);
}
@ -870,18 +1362,28 @@ get_parsed_variant (GVariant *variant,
}
static GVariant *
maybe_parse_snap_hint_value (const gchar *key,
maybe_parse_snap_hint_value (NotifyNotification *notification,
const gchar *key,
GVariant *value)
{
StringParserFunc parse_func = NULL;
if (!_notify_get_snap_path ())
return value;
if (g_strcmp0 (key, "desktop-entry") == 0) {
value = get_parsed_variant (value, try_prepend_desktop);
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) {
value = get_parsed_variant (value, try_prepend_snap);
parse_func = try_prepend_snap;
}
return value;
if (parse_func == NULL) {
return value;
}
return get_parsed_variant (notification, key, value, parse_func);
}
/**
@ -906,7 +1408,7 @@ notify_notification_set_hint (NotifyNotification *notification,
g_return_if_fail (key != NULL && *key != '\0');
if (value != NULL) {
value = maybe_parse_snap_hint_value (key, value);
value = maybe_parse_snap_hint_value (notification, key, value);
g_hash_table_insert (notification->priv->hints,
g_strdup (key),
g_variant_ref_sink (value));
@ -933,6 +1435,10 @@ notify_notification_set_app_name (NotifyNotification *notification,
{
g_return_if_fail (NOTIFY_IS_NOTIFICATION (notification));
if (maybe_warn_portal_unsupported_feature ("App Name")) {
return;
}
g_free (notification->priv->app_name);
notification->priv->app_name = g_strdup (app_name);
@ -1038,7 +1544,11 @@ notify_notification_set_hint_byte_array (NotifyNotification *notification,
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,
@ -1167,6 +1677,28 @@ 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
_notify_notification_has_nondefault_actions (const NotifyNotification *n)
{
@ -1203,6 +1735,12 @@ notify_notification_close (NotifyNotification *notification,
return FALSE;
}
if (_notify_uses_portal_notifications ()) {
return remove_portal_notification (proxy, notification,
NOTIFY_CLOSED_REASON_API_REQUEST,
error);
}
/* FIXME: make this nonblocking! */
result = g_dbus_proxy_call_sync (proxy,
"CloseNotification",
@ -1227,13 +1765,17 @@ notify_notification_close (NotifyNotification *notification,
* Returns the closed reason code for the notification. This is valid only
* after the "closed" signal is emitted.
*
* Returns: The closed reason code.
* Since version 0.8.0 the returned value is of type #NotifyClosedReason.
*
* Returns: An integer representing the closed reason code
* (Since 0.8.0 it's also a #NotifyClosedReason).
*/
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);
g_return_val_if_fail (notification != NULL, NOTIFY_CLOSED_REASON_UNSET);
g_return_val_if_fail (NOTIFY_IS_NOTIFICATION (notification),
NOTIFY_CLOSED_REASON_UNSET);
return notification->priv->closed_reason;
}

View File

@ -55,6 +55,11 @@ typedef struct _NotifyNotification NotifyNotification;
typedef struct _NotifyNotificationClass NotifyNotificationClass;
typedef struct _NotifyNotificationPrivate NotifyNotificationPrivate;
/**
* NotifyNotification:
*
* A passive pop-up notification.
*/
struct _NotifyNotification
{
/*< private >*/
@ -88,11 +93,34 @@ typedef enum
} 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:
* @notification:
* @action:
* @user_data:
* @notification: a #NotifyActionCallback notification
* @action: (transfer none): The activated action name
* @user_data: (nullable) (transfer none): User provided data
*
* An action callback function.
*/
@ -180,6 +208,8 @@ void notify_notification_add_action (NotifyNotificatio
gpointer user_data,
GFreeFunc free_func);
const char *notify_notification_get_activation_token (NotifyNotification *notification);
void notify_notification_clear_actions (NotifyNotification *notification);
gboolean notify_notification_close (NotifyNotification *notification,
GError **error);

View File

@ -43,10 +43,14 @@
static gboolean _initted = FALSE;
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 GList *_active_notifications = NULL;
static int _spec_version_major = 0;
static int _spec_version_minor = 0;
static int _portal_version = 0;
gboolean
_notify_check_spec_version (int major,
@ -74,6 +78,26 @@ _notify_get_server_info (char **ret_name,
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,
"GetServerInformation",
g_variant_new ("()"),
@ -119,6 +143,18 @@ _notify_update_spec_version (GError **error)
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:
@ -130,38 +166,267 @@ _notify_update_spec_version (GError **error)
void
notify_set_app_name (const char *app_name)
{
g_free (_app_name);
_app_name = g_strdup (app_name);
set_app_name (app_name);
}
/**
* notify_init:
* @app_name: The name of the application initializing libnotify.
* @app_name: (nullable): The name of the application initializing libnotify.
*
* 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.
*/
gboolean
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)
return TRUE;
notify_set_app_name (app_name);
if (app_name == NULL) {
GApplication *application;
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
app_name = _notify_get_snap_app ();
if (app_name == NULL) {
app_name = _notify_get_flatpak_app ();
}
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;
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:
*
@ -211,6 +476,15 @@ notify_uninit (void)
_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;
}
@ -227,6 +501,46 @@ notify_is_initted (void)
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:
* @error: (allow-none): a location to store a #GError, or %NULL
@ -242,6 +556,14 @@ _notify_get_proxy (GError **error)
if (_proxy != NULL)
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,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
@ -250,6 +572,8 @@ _notify_get_proxy (GError **error)
NOTIFY_DBUS_CORE_INTERFACE,
NULL,
error);
out:
if (_proxy == NULL) {
return NULL;
}
@ -287,6 +611,15 @@ notify_get_server_caps (void)
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,
"GetCapabilities",
g_variant_new ("()"),

View File

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

View File

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

View File

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

View File

@ -26,12 +26,14 @@
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include <glib-unix.h>
#include <glib/gprintf.h>
#define N_(x) (x)
#define GETTEXT_PACKAGE NULL
static NotifyUrgency urgency = NOTIFY_URGENCY_NORMAL;
static GMainLoop *loop = NULL;
static gboolean
g_option_arg_urgency_cb (const char *option_name,
@ -99,6 +101,26 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
key,
(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 {
*error = g_error_new (G_OPTION_ERROR,
G_OPTION_ERROR_BAD_VALUE,
@ -120,6 +142,70 @@ notify_notification_set_hint_variant (NotifyNotification *notification,
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
main (int argc, char *argv[])
{
@ -130,8 +216,17 @@ main (int argc, char *argv[])
static char *icon_str = NULL;
static char **n_text = 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 hint_error = FALSE, show_error = FALSE;
static gboolean transient = FALSE;
static gboolean wait = FALSE;
static glong expire_timeout = NOTIFY_EXPIRES_DEFAULT;
GOptionContext *opt_ctx;
NotifyNotification *notify;
@ -155,10 +250,26 @@ main (int argc, char *argv[])
{"category", 'c', 0, G_OPTION_ARG_FILENAME, &type,
N_("Specifies the notification category."),
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,
N_
("Specifies basic extra data to pass. Valid types are int, double, string and byte."),
("Specifies basic extra data to pass. Valid types are boolean, int, double, string, byte and variant."),
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,
N_("Version of the package."),
NULL},
@ -172,10 +283,6 @@ main (int argc, char *argv[])
setlocale (LC_ALL, "");
#if !GLIB_CHECK_VERSION (2, 36, 0)
g_type_init ();
#endif
g_set_prgname (argv[0]);
g_log_set_always_fatal (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
@ -217,14 +324,41 @@ main (int argc, char *argv[])
if (!notify_init ("notify-send"))
exit (1);
notify = notify_notification_new (summary,
body,
icon_str);
notify_get_server_info (&server_name,
&server_vendor,
&server_version,
&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_urgency (notify, urgency);
notify_notification_set_timeout (notify, expire_timeout);
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);
/* Set hints */
@ -252,7 +386,7 @@ main (int argc, char *argv[])
if (!retval) {
fprintf (stderr, "%s\n", error->message);
g_error_free (error);
g_clear_error (&error);
hint_error = TRUE;
}
}
@ -263,16 +397,84 @@ 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) {
retval = notify_notification_show (notify, &error);
if (!retval) {
fprintf (stderr, "%s\n", error->message);
g_error_free (error);
g_clear_error (&error);
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));
notify_uninit ();